/* This is a send function for the gnutls handshake * protocol. Just makes sure that all data have been sent. * */ int _gnutls_handshake_io_cache_int(gnutls_session_t session, gnutls_handshake_description_t htype, mbuffer_st * bufel) { mbuffer_head_st *send_buffer; if (IS_DTLS(session)) { bufel->handshake_sequence = session->internals.dtls.hsk_write_seq - 1; } send_buffer = &session->internals.handshake_send_buffer; /* ensure that our epoch does not get garbage collected * before we send all queued messages with it */ bufel->epoch = (uint16_t) _gnutls_epoch_refcount_inc(session, EPOCH_WRITE_CURRENT); bufel->htype = htype; if (bufel->htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) bufel->type = GNUTLS_CHANGE_CIPHER_SPEC; else bufel->type = GNUTLS_HANDSHAKE; _mbuffer_enqueue(send_buffer, bufel); _gnutls_write_log ("HWRITE: enqueued [%s] %d. Total %d bytes.\n", _gnutls_handshake2str(bufel->htype), (int) bufel->msg.size, (int) send_buffer->byte_length); return 0; }
/* Default (unencrypted) send. * For blocking sockets, always returns len or SECFailure, no short writes. * For non-blocking sockets: * Returns positive count if any data was written, else returns SECFailure. * Short writes may occur. Does not return SECWouldBlock. */ int ssl_DefSend(sslSocket *ss, const unsigned char *buf, int len, int flags) { PRFileDesc *lower = ss->fd->lower; int sent = 0; #if NSS_DISABLE_NAGLE_DELAYS /* Although this is overkill, we disable Nagle delays completely for ** SSL sockets. */ if (ss->opt.useSecurity && !ss->delayDisabled) { ssl_EnableNagleDelay(ss, PR_FALSE); /* ignore error */ ss->delayDisabled = 1; } #endif do { int rv = lower->methods->send(lower, (const void *)(buf + sent), len - sent, flags, ss->wTimeout); if (rv < 0) { PRErrorCode err = PR_GetError(); if (err == PR_WOULD_BLOCK_ERROR) { ss->lastWriteBlocked = 1; return sent ? sent : SECFailure; } ss->lastWriteBlocked = 0; MAP_ERROR(PR_CONNECT_ABORTED_ERROR, PR_CONNECT_RESET_ERROR) /* Loser */ return rv; } sent += rv; if (IS_DTLS(ss) && (len > sent)) { /* We got a partial write so just return it */ return sent; } } while (len > sent);
/* Checks if there are pending data in the record buffers. If there are * then it copies the data. */ static int check_buffers (gnutls_session_t session, content_type_t type, uint8_t * data, int data_size, void* seq) { if ((type == GNUTLS_APPLICATION_DATA || type == GNUTLS_HANDSHAKE || type == GNUTLS_CHANGE_CIPHER_SPEC) && _gnutls_record_buffer_get_size (session) > 0) { int ret; ret = _gnutls_record_buffer_get (type, session, data, data_size, seq); if (ret < 0) { if (IS_DTLS(session)) { if (ret == GNUTLS_E_UNEXPECTED_PACKET) { ret = GNUTLS_E_AGAIN; } } gnutls_assert (); return ret; } return ret; } return 0; }
static int _gnutls_dumbfw_send_params(gnutls_session_t session, gnutls_buffer_st * extdata) { int total_size = 0, ret; uint8_t pad[257]; unsigned pad_size; if (session->security_parameters.entity == GNUTLS_SERVER || session->internals.priorities.dumbfw == 0 || IS_DTLS(session) != 0 || (extdata->length < 256 || extdata->length >= 512)) { return 0; } else { /* 256 <= extdata->length < 512 */ pad_size = 512 - extdata->length; memset(pad, 0, pad_size); ret = gnutls_buffer_append_data(extdata, pad, pad_size); if (ret < 0) return gnutls_assert_val(ret); total_size += pad_size; } return total_size; }
/* Here we check if the advertized version is the one we * negotiated in the handshake. */ inline static int record_check_version (gnutls_session_t session, gnutls_handshake_description_t htype, uint8_t version[2]) { if (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO) { /* Reject hello packets with major version higher than 3. */ if (!(IS_DTLS(session)) && version[0] > 3) { gnutls_assert (); _gnutls_record_log ("REC[%p]: INVALID VERSION PACKET: (%d) %d.%d\n", session, htype, version[0], version[1]); return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; } } else if (htype != GNUTLS_HANDSHAKE_SERVER_HELLO && gnutls_protocol_get_version (session) != _gnutls_version_get (version[0], version[1])) { /* Reject record packets that have a different version than the * one negotiated. Note that this version is not protected by any * mac. I don't really think that this check serves any purpose. */ gnutls_assert (); _gnutls_record_log ("REC[%p]: INVALID VERSION PACKET: (%d) %d.%d\n", session, htype, version[0], version[1]); return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; } return 0; }
/* Takes the size of the ClientHello, less the record header, and determines how * much padding is required. */ static unsigned int ssl_CalculatePaddingExtLen(const sslSocket *ss, unsigned int clientHelloLength) { unsigned int recordLength = 1 /* handshake message type */ + 3 /* handshake message length */ + clientHelloLength; unsigned int extensionLen; /* Don't pad for DTLS, for SSLv3, or for renegotiation. */ if (IS_DTLS(ss) || ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_0 || ss->firstHsDone) { return 0; } /* A padding extension may be included to ensure that the record containing * the ClientHello doesn't have a length between 256 and 511 bytes * (inclusive). Initial ClientHello records with such lengths trigger bugs * in F5 devices. */ if (recordLength < 256 || recordLength >= 512) { return 0; } extensionLen = 512 - recordLength; /* Extensions take at least four bytes to encode. Always include at least * one byte of data if we are padding. Some servers will time out or * terminate the connection if the last ClientHello extension is empty. */ if (extensionLen < 5) { extensionLen = 5; } return extensionLen - 4; }
int _gnutls_epoch_alloc (gnutls_session_t session, uint16_t epoch, record_parameters_st ** out) { record_parameters_st **slot; _gnutls_record_log ("REC[%p]: Allocating epoch #%u\n", session, epoch); slot = epoch_get_slot (session, epoch); /* If slot out of range or not empty. */ if (slot == NULL) return gnutls_assert_val (GNUTLS_E_INVALID_REQUEST); if (*slot != NULL) return gnutls_assert_val (GNUTLS_E_INVALID_REQUEST); *slot = gnutls_calloc (1, sizeof (record_parameters_st)); if (*slot == NULL) return gnutls_assert_val (GNUTLS_E_MEMORY_ERROR); (*slot)->epoch = epoch; (*slot)->cipher_algorithm = GNUTLS_CIPHER_UNKNOWN; (*slot)->mac_algorithm = GNUTLS_MAC_UNKNOWN; (*slot)->compression_algorithm = GNUTLS_COMP_UNKNOWN; if (IS_DTLS (session)) _gnutls_write_uint16 (epoch, UINT64DATA((*slot)->write.sequence_number)); if (out != NULL) *out = *slot; return 0; }
/* Checks and retrieves any pending data in the application data record buffers. */ static int check_packet_buffers(gnutls_session_t session, content_type_t type, gnutls_packet_t *packet) { if (_gnutls_record_buffer_get_size(session) > 0) { int ret; ret = _gnutls_record_buffer_get_packet(type, session, packet); if (ret < 0) { if (IS_DTLS(session)) { if (ret == GNUTLS_E_UNEXPECTED_PACKET) { ret = GNUTLS_E_AGAIN; } } gnutls_assert(); return ret; } return ret; } *packet = NULL; return 0; }
/** * gnutls_record_send: * @session: is a #gnutls_session_t structure. * @data: contains the data to send * @data_size: is the length of the data * * This function has the similar semantics with send(). The only * difference is that it accepts a GnuTLS session, and uses different * error codes. * Note that if the send buffer is full, send() will block this * function. See the send() documentation for more information. * * You can replace the default push function which is send(), by using * gnutls_transport_set_push_function(). * * If the EINTR is returned by the internal push function * then %GNUTLS_E_INTERRUPTED will be returned. If * %GNUTLS_E_INTERRUPTED or %GNUTLS_E_AGAIN is returned, you must * call this function again, with the exact same parameters; alternatively * you could provide a %NULL pointer for data, and 0 for * size. cf. gnutls_record_get_direction(). * * Note that in DTLS this function will return the %GNUTLS_E_LARGE_PACKET * error code if the send data exceed the data MTU value - as returned * by gnutls_dtls_get_data_mtu(). The errno value EMSGSIZE * also maps to %GNUTLS_E_LARGE_PACKET. * Note that since 3.2.13 this function can be called under cork in DTLS * mode, and will refuse to send data over the MTU size by returning * %GNUTLS_E_LARGE_PACKET. * * Returns: The number of bytes sent, or a negative error code. The * number of bytes sent might be less than @data_size. The maximum * number of bytes this function can send in a single call depends * on the negotiated maximum record size. **/ ssize_t gnutls_record_send(gnutls_session_t session, const void *data, size_t data_size) { if (session->internals.record_flush_mode == RECORD_FLUSH) { return _gnutls_send_int(session, GNUTLS_APPLICATION_DATA, -1, EPOCH_WRITE_CURRENT, data, data_size, MBUFFER_FLUSH); } else { /* GNUTLS_CORKED */ int ret; if (IS_DTLS(session)) { if (data_size + session->internals.record_presend_buffer.length > gnutls_dtls_get_data_mtu(session)) { return gnutls_assert_val(GNUTLS_E_LARGE_PACKET); } } ret = _gnutls_buffer_append_data(&session->internals. record_presend_buffer, data, data_size); if (ret < 0) return gnutls_assert_val(ret); return data_size; } }
int _gnutls_record_buffer_get_packet(content_type_t type, gnutls_session_t session, gnutls_packet_t *packet) { mbuffer_st *bufel; bufel = _mbuffer_head_pop_first(&session->internals.record_buffer); if (bufel == NULL) return gnutls_assert_val (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); if (type != bufel->type) { if (IS_DTLS(session)) _gnutls_audit_log(session, "Discarded unexpected %s (%d) packet (expecting: %s)\n", _gnutls_packet2str(bufel->type), (int) bufel->type, _gnutls_packet2str(type)); _mbuffer_head_remove_bytes(&session->internals. record_buffer, bufel->msg.size); return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); } *packet = bufel; return bufel->msg.size - bufel->mark; }
/* Checks whether there are received data within * a timeframe. * * Returns 0 if data were received, GNUTLS_E_TIMEDOUT * on timeout and a negative error code on error. */ int _gnutls_io_check_recv(gnutls_session_t session, unsigned int ms) { gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; int ret = 0, err; if (unlikely (session->internals.pull_timeout_func == gnutls_system_recv_timeout && session->internals.pull_func != system_read)) { _gnutls_debug_log("The pull function has been replaced but not the pull timeout."); return gnutls_assert_val(GNUTLS_E_PULL_ERROR); } reset_errno(session); ret = session->internals.pull_timeout_func(fd, ms); if (ret == -1) { err = get_errno(session); _gnutls_read_log ("READ_TIMEOUT: %d returned from %p, errno=%d (timeout: %u)\n", (int) ret, fd, err, ms); return errno_to_gerr(err, IS_DTLS(session)); } if (ret > 0) return 0; else return GNUTLS_E_TIMEDOUT; }
/* returns ciphertext which contains the headers too. This also * calculates the size in the header field. * * If random pad != 0 then the random pad data will be appended. */ int _gnutls_encrypt (gnutls_session_t session, const opaque * headers, size_t headers_size, const opaque * data, size_t data_size, opaque * ciphertext, size_t ciphertext_size, content_type_t type, record_parameters_st * params) { gnutls_datum_t comp; int free_comp = 0; int ret; if (data_size == 0 || is_write_comp_null (params) == 0) { comp.data = (opaque*)data; comp.size = data_size; } else { /* Here comp is allocated and must be * freed. */ free_comp = 1; comp.size = ciphertext_size - headers_size; comp.data = gnutls_malloc(comp.size); if (comp.data == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ret = _gnutls_compress( ¶ms->write.compression_state, data, data_size, comp.data, comp.size); if (ret < 0) { gnutls_free(comp.data); return gnutls_assert_val(ret); } comp.size = ret; } ret = compressed_to_ciphertext (session, &ciphertext[headers_size], ciphertext_size - headers_size, &comp, type, params); if (free_comp) gnutls_free(comp.data); if (ret < 0) return gnutls_assert_val(ret); /* copy the headers */ memcpy (ciphertext, headers, headers_size); if(IS_DTLS(session)) _gnutls_write_uint16 (ret, &ciphertext[11]); else _gnutls_write_uint16 (ret, &ciphertext[3]); return ret + headers_size; }
/* returns the last stored handshake packet. */ static int get_last_packet(gnutls_session_t session, gnutls_handshake_description_t htype, handshake_buffer_st * hsk) { handshake_buffer_st* recv_buf = session->internals.handshake_recv_buffer; if (IS_DTLS(session)) { if (session->internals.handshake_recv_buffer_size == 0 || (session->internals.dtls.hsk_read_seq != recv_buf[LAST_ELEMENT].sequence)) goto timeout; if (htype != recv_buf[LAST_ELEMENT].htype) { hsk->htype = recv_buf[LAST_ELEMENT].htype; return gnutls_assert_val(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); } else if ((recv_buf[LAST_ELEMENT].start_offset == 0 && recv_buf[LAST_ELEMENT].end_offset == recv_buf[LAST_ELEMENT].length -1) || recv_buf[LAST_ELEMENT].length == 0) { session->internals.dtls.hsk_read_seq++; _gnutls_handshake_buffer_move(hsk, &recv_buf[LAST_ELEMENT]); session->internals.handshake_recv_buffer_size--; return 0; } else goto timeout; } else /* TLS */ { if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length == recv_buf[0].data.length) { if (cmp_hsk_types(htype, recv_buf[0].htype) == 0) { hsk->htype = recv_buf[LAST_ELEMENT].htype; return gnutls_assert_val(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); } _gnutls_handshake_buffer_move(hsk, &recv_buf[0]); session->internals.handshake_recv_buffer_size--; return 0; } else return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); } timeout: if (time(0)-session->internals.dtls.handshake_start_time > session->internals.dtls.total_timeout/1000) return gnutls_assert_val(GNUTLS_E_TIMEDOUT); else { if (session->internals.dtls.blocking != 0) millisleep(50); return gnutls_assert_val(GNUTLS_E_AGAIN); } }
/* Increments the sequence value */ inline static int sequence_increment(gnutls_session_t session, uint64 * value) { if (IS_DTLS(session)) { return _gnutls_uint48pp(value); } else { return _gnutls_uint64pp(value); } }
/* This function is like read. But it does not return -1 on error. * It does return gnutls_errno instead. * * Flags are only used if the default recv() function is being used. */ static ssize_t _gnutls_read (gnutls_session_t session, mbuffer_st **bufel, size_t size, gnutls_pull_func pull_func) { if (IS_DTLS (session)) /* Size is not passed, since a whole datagram will be read. */ return _gnutls_dgram_read (session, bufel, pull_func); else return _gnutls_stream_read (session, bufel, size, pull_func); }
PRInt32 tls13_SendShortHeaderXtn(const sslSocket *ss, TLSExtensionData *xtnData, PRBool append, PRUint32 maxBytes) { PRUint32 extension_len = 2 + 2; /* Type + length (0). */ if (!ss->opt.enableShortHeaders) { return 0; } /* Presently this is incompatible with 0-RTT. We will fix if * it becomes more than an experiment. */ if (ss->opt.enable0RttData) { return 0; } if (IS_DTLS(ss)) { return 0; } /* Don't send this if TLS 1.3 isn't at least possible. */ if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3) { /* This should only happen on the client. */ PORT_Assert(!ss->sec.isServer); return 0; } SSL_TRC(3, ("%d: TLS13[%d]: send short_header extension", SSL_GETPID(), ss->fd)); if (maxBytes < extension_len) { PORT_Assert(0); return 0; } if (append) { SECStatus rv; rv = ssl3_ExtAppendHandshakeNumber(ss, ssl_tls13_short_header_xtn, 2); if (rv != SECSuccess) return -1; rv = ssl3_ExtAppendHandshakeNumber(ss, 0, 2); if (rv != SECSuccess) return -1; xtnData->advertised[xtnData->numAdvertised++] = ssl_tls13_short_header_xtn; } return extension_len; }
/* This function writes the data that are left in the * Handshake write buffer (ie. because the previous write was * interrupted. * */ ssize_t _gnutls_handshake_io_write_flush (gnutls_session_t session) { mbuffer_head_st *const send_buffer = &session->internals.handshake_send_buffer; gnutls_datum_t msg; int ret; uint16_t epoch; ssize_t total = 0; mbuffer_st *cur; _gnutls_write_log ("HWRITE FLUSH: %d bytes in buffer.\n", (int) send_buffer->byte_length); if (IS_DTLS(session)) return _dtls_transmit(session); for (cur = _mbuffer_head_get_first (send_buffer, &msg); cur != NULL; cur = _mbuffer_head_get_first (send_buffer, &msg)) { epoch = cur->epoch; ret = _gnutls_send_int (session, cur->type, cur->htype, epoch, msg.data, msg.size, 0); if (ret >= 0) { total += ret; ret = _mbuffer_head_remove_bytes (send_buffer, ret); if (ret == 1) _gnutls_epoch_refcount_dec(session, epoch); _gnutls_write_log ("HWRITE: wrote %d bytes, %d bytes left.\n", ret, (int) send_buffer->byte_length); } else { _gnutls_write_log ("HWRITE error: code %d, %d bytes left.\n", ret, (int) send_buffer->byte_length); gnutls_assert (); return ret; } } return _gnutls_io_write_flush (session); }
/* returns ciphertext which contains the headers too. This also * calculates the size in the header field. * */ int _gnutls_encrypt(gnutls_session_t session, const uint8_t *data, size_t data_size, size_t min_pad, mbuffer_st *bufel, content_type_t type, record_parameters_st *params) { gnutls_datum_t plaintext; const version_entry_st *vers = get_version(session); int ret; plaintext.data = (uint8_t *) data; plaintext.size = data_size; if (vers && vers->tls13_sem) { /* it fills the header, as it is included in the authenticated * data of the AEAD cipher. */ ret = encrypt_packet_tls13(session, _mbuffer_get_udata_ptr(bufel), _mbuffer_get_udata_size(bufel), &plaintext, min_pad, type, params); if (ret < 0) return gnutls_assert_val(ret); } else { ret = encrypt_packet(session, _mbuffer_get_udata_ptr(bufel), _mbuffer_get_udata_size (bufel), &plaintext, min_pad, type, params); if (ret < 0) return gnutls_assert_val(ret); } if (IS_DTLS(session)) _gnutls_write_uint16(ret, ((uint8_t *) _mbuffer_get_uhead_ptr(bufel)) + 11); else _gnutls_write_uint16(ret, ((uint8_t *) _mbuffer_get_uhead_ptr(bufel)) + 3); _mbuffer_set_udata_size(bufel, ret); _mbuffer_set_uhead_size(bufel, 0); return _mbuffer_get_udata_size(bufel); }
int _gnutls_record_buffer_get(content_type_t type, gnutls_session_t session, uint8_t * data, size_t length, uint8_t seq[8]) { gnutls_datum_t msg; mbuffer_st *bufel; if (length == 0 || data == NULL) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); if (bufel == NULL) return gnutls_assert_val (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); if (type != bufel->type) { if (IS_DTLS(session)) _gnutls_audit_log(session, "Discarded unexpected %s (%d) packet (expecting: %s (%d))\n", _gnutls_packet2str(bufel->type), (int) bufel->type, _gnutls_packet2str(type), (int) type); else _gnutls_debug_log("received unexpected packet: %s(%d)\n", _gnutls_packet2str(bufel->type), (int)bufel->type); _mbuffer_head_remove_bytes(&session->internals. record_buffer, msg.size); return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); } if (msg.size <= length) length = msg.size; if (seq) memcpy(seq, bufel->record_sequence.i, 8); memcpy(data, msg.data, length); _mbuffer_head_remove_bytes(&session->internals.record_buffer, length); return length; }
SECStatus tls13_HandleShortHeaderXtn( const sslSocket *ss, TLSExtensionData *xtnData, PRUint16 ex_type, SECItem *data) { SSL_TRC(3, ("%d: TLS13[%d]: handle short_header extension", SSL_GETPID(), ss->fd)); /* The client might have asked for this, but we didn't negotiate TLS 1.3. */ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { return SECSuccess; } /* Presently this is incompatible with 0-RTT. We will fix if * it becomes more than an experiment. */ if (ss->opt.enable0RttData) { return SECSuccess; } if (IS_DTLS(ss)) { PORT_SetError(SSL_ERROR_EXTENSION_DISALLOWED_FOR_VERSION); return SECFailure; } if (data->len) { PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE); return SECFailure; } if (!ss->opt.enableShortHeaders) { /* Ignore. */ return SECSuccess; } /* Keep track of negotiated extensions. */ xtnData->negotiated[xtnData->numNegotiated++] = ex_type; if (ss->sec.isServer) { SECStatus rv; rv = ssl3_RegisterExtensionSender(ss, xtnData, ssl_tls13_short_header_xtn, tls13_SendShortHeaderXtn); if (rv != SECSuccess) { return SECFailure; } } return SECSuccess; }
int _gnutls_supported_compression_methods(gnutls_session_t session, uint8_t * comp, size_t comp_size) { unsigned int i, j; int tmp; if (comp_size < SUPPORTED_COMPRESSION_METHODS) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); for (i = j = 0; i < SUPPORTED_COMPRESSION_METHODS; i++) { if (IS_DTLS(session) && session->internals.priorities.compression.priority[i] != GNUTLS_COMP_NULL) { gnutls_assert(); continue; } tmp = _gnutls_compression_get_num(session-> internals.priorities. compression.priority[i]); /* remove private compression algorithms, if requested. */ if (tmp == -1 || (tmp >= MIN_PRIVATE_COMP_ALGO && session->internals.enable_private == 0)) { gnutls_assert(); continue; } comp[j] = (uint8_t) tmp; j++; } if (j == 0) { gnutls_assert(); return GNUTLS_E_NO_COMPRESSION_ALGORITHMS; } return j; }
/* @total: The sum of the data in giovec */ static ssize_t _gnutls_writev(gnutls_session_t session, const giovec_t * giovec, unsigned giovec_cnt, unsigned total) { int i; bool is_dtls = IS_DTLS(session); unsigned no_writev = 0; gnutls_transport_ptr_t fd = session->internals.transport_send_ptr; reset_errno(session); if (session->internals.vec_push_func != NULL) { if (is_dtls && giovec_cnt > 1) { if (total > session->internals.dtls.mtu) { no_writev = 1; } } if (no_writev == 0) { i = session->internals.vec_push_func(fd, giovec, giovec_cnt); } else { i = _gnutls_writev_emu(session, fd, giovec, giovec_cnt, 1); } } else if (session->internals.push_func != NULL) { i = _gnutls_writev_emu(session, fd, giovec, giovec_cnt, 0); } else return gnutls_assert_val(GNUTLS_E_PUSH_ERROR); if (i == -1) { int err = get_errno(session); _gnutls_debug_log("WRITE: %d returned from %p, errno: %d\n", i, fd, err); return errno_to_gerr(err, is_dtls); } return i; }
/* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. */ ssize_t _gnutls_handshake_io_recv_int (gnutls_session_t session, gnutls_handshake_description_t htype, handshake_buffer_st * hsk) { int ret; ret = get_last_packet(session, htype, hsk); if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { return gnutls_assert_val(ret); } /* try using the already existing records before * trying to receive. */ ret = parse_record_buffered_msgs(session, htype, hsk); if (IS_DTLS(session)) { if (ret >= 0) return ret; } else { if ((ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE && ret < 0) || ret >= 0) return gnutls_assert_val(ret); } /* if we don't have a complete message waiting for us, try * receiving more */ ret = _gnutls_recv_in_buffers(session, GNUTLS_HANDSHAKE, htype); if (ret < 0) return gnutls_assert_val_fatal(ret); return parse_record_buffered_msgs(session, htype, hsk); }
/* Caller must hold the SpecWriteLock. */ SECStatus ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir) { ssl3CipherSpec *spec; PORT_Assert(ss->opt.noLocks || ssl_HaveSpecWriteLock(ss)); spec = ssl_CreateCipherSpec(ss, dir); if (!spec) { return SECFailure; } /* Set default versions. This value will be used to generate and send * alerts if a version is not negotiated. These values are overridden when * sending a ClientHello and when a version is negotiated. */ spec->version = SSL_LIBRARY_VERSION_TLS_1_0; spec->recordVersion = IS_DTLS(ss) ? SSL_LIBRARY_VERSION_DTLS_1_0_WIRE : SSL_LIBRARY_VERSION_TLS_1_0; spec->cipherDef = &ssl_bulk_cipher_defs[cipher_null]; PORT_Assert(spec->cipherDef->cipher == cipher_null); spec->macDef = &ssl_mac_defs[ssl_mac_null]; PORT_Assert(spec->macDef->mac == ssl_mac_null); spec->cipher = Null_Cipher; spec->phase = "cleartext"; dtls_InitRecvdRecords(&spec->recvdRecords); ssl_SaveCipherSpec(ss, spec); if (dir == CipherSpecRead) { ss->ssl3.crSpec = spec; } else { ss->ssl3.cwSpec = spec; } return SECSuccess; }
static int parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel, handshake_buffer_st * hsk) { uint8_t *dataptr = NULL; /* for realloc */ size_t handshake_header_size = HANDSHAKE_HEADER_SIZE(session), data_size, frag_size; /* Note: SSL2_HEADERS == 1 */ if (_mbuffer_get_udata_size(bufel) < handshake_header_size) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); dataptr = _mbuffer_get_udata_ptr(bufel); /* if reading a client hello of SSLv2 */ #ifdef ENABLE_SSL2 if (unlikely (!IS_DTLS(session) && bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2)) { handshake_header_size = SSL2_HEADERS; /* we've already read one byte */ frag_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; /* we've read the first byte */ if (dataptr[0] != GNUTLS_HANDSHAKE_CLIENT_HELLO) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); hsk->rtype = hsk->htype = GNUTLS_HANDSHAKE_CLIENT_HELLO_V2; hsk->sequence = 0; hsk->start_offset = 0; hsk->length = frag_size; } else #endif { /* TLS or DTLS handshake headers */ hsk->rtype = hsk->htype = dataptr[0]; /* we do not use DECR_LEN because we know * that the packet has enough data. */ hsk->length = _gnutls_read_uint24(&dataptr[1]); if (IS_DTLS(session)) { hsk->sequence = _gnutls_read_uint16(&dataptr[4]); hsk->start_offset = _gnutls_read_uint24(&dataptr[6]); frag_size = _gnutls_read_uint24(&dataptr[9]); } else { hsk->sequence = 0; hsk->start_offset = 0; frag_size = MIN((_mbuffer_get_udata_size(bufel) - handshake_header_size), hsk->length); } /* TLS1.3: distinguish server hello versus hello retry request. * The epitome of slick protocol design. */ if (hsk->htype == GNUTLS_HANDSHAKE_SERVER_HELLO && hsk->start_offset == 0 && !IS_DTLS(session)) { if (_mbuffer_get_udata_size(bufel) > handshake_header_size+2+GNUTLS_RANDOM_SIZE && memcmp(dataptr+handshake_header_size+2, HRR_RANDOM, GNUTLS_RANDOM_SIZE) == 0) { hsk->htype = GNUTLS_HANDSHAKE_HELLO_RETRY_REQUEST; } } } data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; if (frag_size > 0) hsk->end_offset = hsk->start_offset + frag_size - 1; else hsk->end_offset = 0; _gnutls_handshake_log ("HSK[%p]: %s (%u) was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n", session, _gnutls_handshake2str(hsk->htype), (unsigned) hsk->htype, (int) hsk->length, (int) data_size, hsk->start_offset, (int) frag_size, (int) hsk->sequence); hsk->header_size = handshake_header_size; memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel), handshake_header_size); if (hsk->length > 0 && (frag_size > data_size || (frag_size > 0 && hsk->end_offset >= hsk->length))) { return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); } else if (hsk->length == 0 && hsk->end_offset != 0 && hsk->start_offset != 0) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); return handshake_header_size; }
/* * @ms: a pointer to the number of milliseconds to wait for data. Use zero or NULL for indefinite. * * This function is like recv(with MSG_PEEK). But it does not return -1 on error. * It does return gnutls_errno instead. * This function reads data from the socket and keeps them in a buffer, of up to * max_record_recv_size. * * This is not a general purpose function. It returns EXACTLY the data requested, * which are stored in a local (in the session) buffer. * * If the @ms parameter is non zero then this function will return before * the given amount of milliseconds or return GNUTLS_E_TIMEDOUT. * */ ssize_t _gnutls_io_read_buffered(gnutls_session_t session, size_t total, content_type_t recv_type, unsigned int *ms) { ssize_t ret; size_t min; mbuffer_st *bufel = NULL; size_t recvdata, readsize; if (total > max_record_recv_size(session) || total == 0) { gnutls_assert(); return GNUTLS_E_RECORD_OVERFLOW; } /* calculate the actual size, ie. get the minimum of the * buffered data and the requested data. */ min = MIN(session->internals.record_recv_buffer.byte_length, total); if (min > 0) { /* if we have enough buffered data * then just return them. */ if (min == total) { return min; } } /* min is over zero. recvdata is the data we must * receive in order to return the requested data. */ recvdata = total - min; readsize = recvdata; /* Check if the previously read data plus the new data to * receive are longer than the maximum receive buffer size. */ if ((session->internals.record_recv_buffer.byte_length + recvdata) > max_record_recv_size(session)) { gnutls_assert(); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } /* READ DATA */ if (readsize > 0) { ret = _gnutls_read(session, &bufel, readsize, session->internals.pull_func, ms); /* return immediately if we got an interrupt or eagain * error. */ if (ret < 0) { return gnutls_assert_val(ret); } if (ret == 0) /* EOF */ return gnutls_assert_val(0); /* copy fresh data to our buffer. */ _gnutls_read_log ("RB: Have %d bytes into buffer. Adding %d bytes.\n", (int) session->internals.record_recv_buffer. byte_length, (int) ret); _gnutls_read_log("RB: Requested %d bytes\n", (int) total); _mbuffer_enqueue(&session->internals.record_recv_buffer, bufel); if (IS_DTLS(session)) ret = MIN(total, session->internals.record_recv_buffer. byte_length); else ret = session->internals.record_recv_buffer. byte_length; if ((ret > 0) && ((size_t) ret < total)) /* Short Read */ return gnutls_assert_val(GNUTLS_E_AGAIN); else return ret; } else return gnutls_assert_val(0); }
/* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. */ ssize_t _gnutls_handshake_io_recv_int(gnutls_session_t session, gnutls_handshake_description_t htype, handshake_buffer_st * hsk, unsigned int optional) { int ret; unsigned int tleft = 0; int retries = 7; ret = get_last_packet(session, htype, hsk, optional); if (ret != GNUTLS_E_AGAIN && ret != GNUTLS_E_INTERRUPTED && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE && ret != GNUTLS_E_INT_CHECK_AGAIN) { return gnutls_assert_val(ret); } /* try using the already existing records before * trying to receive. */ ret = _gnutls_parse_record_buffered_msgs(session); if (ret == 0) { ret = get_last_packet(session, htype, hsk, optional); } if (IS_DTLS(session)) { if (ret >= 0) return ret; } else { if ((ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE && ret < 0) || ret >= 0) return gnutls_assert_val(ret); } if (htype != (gnutls_handshake_description_t) -1) { ret = handshake_remaining_time(session); if (ret < 0) return gnutls_assert_val(ret); tleft = ret; } do { /* if we don't have a complete message waiting for us, try * receiving more */ ret = _gnutls_recv_in_buffers(session, GNUTLS_HANDSHAKE, htype, tleft); if (ret < 0) return gnutls_assert_val_fatal(ret); ret = _gnutls_parse_record_buffered_msgs(session); if (ret == 0) { ret = get_last_packet(session, htype, hsk, optional); } /* we put an upper limit (retries) to the number of partial handshake * messages in a record packet. */ } while(IS_DTLS(session) && ret == GNUTLS_E_INT_CHECK_AGAIN && retries-- > 0); if (unlikely(IS_DTLS(session) && ret == GNUTLS_E_INT_CHECK_AGAIN)) { ret = gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS); } return ret; }
/* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. * * htype is the next handshake packet expected. */ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session) { gnutls_datum_t msg; mbuffer_st *bufel = NULL, *prev = NULL; int ret; size_t data_size; handshake_buffer_st *recv_buf = session->internals.handshake_recv_buffer; bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); if (bufel == NULL) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; if (!IS_DTLS(session)) { ssize_t append, header_size; do { if (bufel->type != GNUTLS_HANDSHAKE) return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET); if (unlikely (session->internals.handshake_recv_buffer_size == 0 && msg.size < HANDSHAKE_HEADER_SIZE(session) && session->internals.handshake_header_recv_buffer.byte_length < HANDSHAKE_HEADER_SIZE(session) - msg.size)) { bufel = _mbuffer_head_pop_first(&session->internals.record_buffer); _mbuffer_enqueue(&session->internals.handshake_header_recv_buffer, bufel); break; } else if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length > recv_buf[0].data.length) { /* this is the rest of a previous message */ append = MIN(msg.size, recv_buf[0].length - recv_buf[0].data.length); ret = _gnutls_buffer_append_data(&recv_buf [0].data, msg.data, append); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_head_remove_bytes(&session-> internals. record_buffer, append); } else { /* received new message */ if (unlikely (session->internals. handshake_header_recv_buffer.length > 0)) { bufel = _mbuffer_head_pop_first(&session->internals. record_buffer); _mbuffer_enqueue(&session->internals. handshake_header_recv_buffer, bufel); ret = _mbuffer_linearize_align16(&session->internals. handshake_header_recv_buffer, get_total_headers(session)); if (ret < 0) return gnutls_assert_val(ret); bufel = _mbuffer_head_pop_first(&session->internals. handshake_header_recv_buffer); _mbuffer_head_push_first(&session->internals. record_buffer, bufel); } ret = parse_handshake_header(session, bufel, &recv_buf[0]); if (ret < 0) return gnutls_assert_val(ret); header_size = ret; session->internals. handshake_recv_buffer_size = 1; _mbuffer_set_uhead_size(bufel, header_size); data_size = MIN(recv_buf[0].length, _mbuffer_get_udata_size(bufel)); ret = _gnutls_buffer_append_data(&recv_buf [0].data, _mbuffer_get_udata_ptr (bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_set_uhead_size(bufel, 0); _mbuffer_head_remove_bytes(&session-> internals. record_buffer, data_size + header_size); } /* if packet is complete then return it */ if (recv_buf[0].length == recv_buf[0].data.length) { return 0; } bufel = _mbuffer_head_get_first(&session->internals. record_buffer, &msg); } while (bufel != NULL); /* if we are here it means that the received packets were not * enough to complete the handshake packet. */ return gnutls_assert_val(GNUTLS_E_AGAIN); } else { /* DTLS */ handshake_buffer_st tmp; do { /* we now * 0. parse headers * 1. insert to handshake_recv_buffer * 2. sort handshake_recv_buffer on sequence numbers * 3. return first packet if completed or GNUTLS_E_AGAIN. */ do { if (bufel->type != GNUTLS_HANDSHAKE) { gnutls_assert(); goto next; /* ignore packet */ } _gnutls_handshake_buffer_init(&tmp); ret = parse_handshake_header(session, bufel, &tmp); if (ret < 0) { gnutls_assert(); _gnutls_audit_log(session, "Invalid handshake packet headers. Discarding.\n"); break; } _mbuffer_consume(&session->internals. record_buffer, bufel, ret); data_size = MIN(tmp.length, tmp.end_offset - tmp.start_offset + 1); ret = _gnutls_buffer_append_data(&tmp.data, _mbuffer_get_udata_ptr (bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_consume(&session->internals. record_buffer, bufel, data_size); ret = merge_handshake_packet(session, &tmp); if (ret < 0) return gnutls_assert_val(ret); } while (_mbuffer_get_udata_size(bufel) > 0); prev = bufel; bufel = _mbuffer_dequeue(&session->internals. record_buffer, bufel); _mbuffer_xfree(&prev); continue; next: bufel = _mbuffer_head_get_next(bufel, NULL); } while (bufel != NULL); /* sort in descending order */ if (session->internals.handshake_recv_buffer_size > 1) qsort(recv_buf, session->internals. handshake_recv_buffer_size, sizeof(recv_buf[0]), handshake_compare); while (session->internals.handshake_recv_buffer_size > 0 && recv_buf[LAST_ELEMENT].sequence < session->internals.dtls.hsk_read_seq) { _gnutls_audit_log(session, "Discarded replayed handshake packet with sequence %d\n", recv_buf[LAST_ELEMENT].sequence); _gnutls_handshake_buffer_clear(&recv_buf [LAST_ELEMENT]); session->internals.handshake_recv_buffer_size--; } return 0; } }
/* returns the last stored handshake packet. */ static int get_last_packet(gnutls_session_t session, gnutls_handshake_description_t htype, handshake_buffer_st * hsk, unsigned int optional) { handshake_buffer_st *recv_buf = session->internals.handshake_recv_buffer; if (IS_DTLS(session)) { if (session->internals.handshake_recv_buffer_size == 0 || (session->internals.dtls.hsk_read_seq != recv_buf[LAST_ELEMENT].sequence)) goto timeout; if (htype != recv_buf[LAST_ELEMENT].htype) { if (optional == 0) _gnutls_audit_log(session, "Received unexpected handshake message '%s' (%d). Expected '%s' (%d)\n", _gnutls_handshake2str (recv_buf[0].htype), (int) recv_buf[0].htype, _gnutls_handshake2str (htype), (int) htype); return gnutls_assert_val (GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); } else if ((recv_buf[LAST_ELEMENT].start_offset == 0 && recv_buf[LAST_ELEMENT].end_offset == recv_buf[LAST_ELEMENT].length - 1) || recv_buf[LAST_ELEMENT].length == 0) { session->internals.dtls.hsk_read_seq++; _gnutls_handshake_buffer_move(hsk, &recv_buf [LAST_ELEMENT]); session->internals.handshake_recv_buffer_size--; return 0; } else { /* if we don't have a complete handshake message, but we * have queued data waiting, try again to reconstruct the * handshake packet, using the queued */ if (recv_buf[LAST_ELEMENT].end_offset != recv_buf[LAST_ELEMENT].length - 1 && record_check_unprocessed(session) > 0) return gnutls_assert_val(GNUTLS_E_INT_CHECK_AGAIN); else goto timeout; } } else { /* TLS */ if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length == recv_buf[0].data.length) { if (cmp_hsk_types(htype, recv_buf[0].htype) == 0) { return gnutls_assert_val (GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); } _gnutls_handshake_buffer_move(hsk, &recv_buf[0]); session->internals.handshake_recv_buffer_size--; return 0; } else return gnutls_assert_val (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); } timeout: RETURN_DTLS_EAGAIN_OR_TIMEOUT(session, 0); }
static int parse_handshake_header(gnutls_session_t session, mbuffer_st * bufel, handshake_buffer_st * hsk) { uint8_t *dataptr = NULL; /* for realloc */ size_t handshake_header_size = HANDSHAKE_HEADER_SIZE(session), data_size; /* Note: SSL2_HEADERS == 1 */ if (_mbuffer_get_udata_size(bufel) < handshake_header_size) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); dataptr = _mbuffer_get_udata_ptr(bufel); /* if reading a client hello of SSLv2 */ #ifdef ENABLE_SSL2 if (unlikely (!IS_DTLS(session) && bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2)) { handshake_header_size = SSL2_HEADERS; /* we've already read one byte */ hsk->length = _mbuffer_get_udata_size(bufel) - handshake_header_size; /* we've read the first byte */ if (dataptr[0] != GNUTLS_HANDSHAKE_CLIENT_HELLO) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); hsk->htype = GNUTLS_HANDSHAKE_CLIENT_HELLO_V2; hsk->sequence = 0; hsk->start_offset = 0; hsk->end_offset = hsk->length; } else #endif { /* TLS or DTLS handshake headers */ hsk->htype = dataptr[0]; /* we do not use DECR_LEN because we know * that the packet has enough data. */ hsk->length = _gnutls_read_uint24(&dataptr[1]); handshake_header_size = HANDSHAKE_HEADER_SIZE(session); if (IS_DTLS(session)) { hsk->sequence = _gnutls_read_uint16(&dataptr[4]); hsk->start_offset = _gnutls_read_uint24(&dataptr[6]); hsk->end_offset = hsk->start_offset + _gnutls_read_uint24(&dataptr[9]); } else { hsk->sequence = 0; hsk->start_offset = 0; hsk->end_offset = MIN((_mbuffer_get_udata_size(bufel) - handshake_header_size), hsk->length); } } data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size; /* make the length offset */ if (hsk->end_offset > 0) hsk->end_offset--; _gnutls_handshake_log ("HSK[%p]: %s (%u) was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n", session, _gnutls_handshake2str(hsk->htype), (unsigned) hsk->htype, (int) hsk->length, (int) data_size, hsk->start_offset, hsk->end_offset - hsk->start_offset + 1, (int) hsk->sequence); hsk->header_size = handshake_header_size; memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel), handshake_header_size); if (hsk->length > 0 && (hsk->end_offset - hsk->start_offset >= data_size)) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); if (hsk->length > 0 && (hsk->start_offset >= hsk->end_offset || hsk->end_offset - hsk->start_offset >= data_size || hsk->end_offset >= hsk->length)) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); else if (hsk->length == 0 && hsk->end_offset != 0 && hsk->start_offset != 0) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); return handshake_header_size; }