int s2n_record_writev()

in tls/s2n_record_write.c [356:632]


int s2n_record_writev(struct s2n_connection *conn, uint8_t content_type, const struct iovec *in, int in_count, size_t offs, size_t to_write)
{
    if (conn->ktls_send_enabled) {
        return s2n_ktls_record_writev(conn, content_type, in, in_count, offs, to_write);
    }

    struct s2n_blob iv = { 0 };
    uint8_t padding = 0;
    uint16_t block_size = 0;
    uint8_t aad_iv[S2N_TLS_MAX_IV_LEN] = { 0 };

    /* In TLS 1.3, handle CCS message as unprotected records */
    struct s2n_crypto_parameters *current_client_crypto = conn->client;
    struct s2n_crypto_parameters *current_server_crypto = conn->server;
    if (conn->actual_protocol_version == S2N_TLS13 && content_type == TLS_CHANGE_CIPHER_SPEC) {
        POSIX_ENSURE_REF(conn->initial);
        conn->client = conn->initial;
        conn->server = conn->initial;
    }

    uint8_t *sequence_number = conn->server->server_sequence_number;
    struct s2n_session_key *session_key = &conn->server->server_key;
    const struct s2n_cipher_suite *cipher_suite = conn->server->cipher_suite;
    uint8_t *implicit_iv = conn->server->server_implicit_iv;

    if (conn->mode == S2N_CLIENT) {
        sequence_number = conn->client->client_sequence_number;
        session_key = &conn->client->client_key;
        cipher_suite = conn->client->cipher_suite;
        implicit_iv = conn->client->client_implicit_iv;
    }

    /* The NULL stream cipher MUST NEVER be used for ApplicationData.
     * Writing ApplicationData unencrypted defeats the purpose of TLS. */
    if (cipher_suite->record_alg->cipher == &s2n_null_cipher) {
        POSIX_ENSURE(content_type != TLS_APPLICATION_DATA, S2N_ERR_ENCRYPT);
    }

    const int is_tls13_record = cipher_suite->record_alg->flags & S2N_TLS13_RECORD_AEAD_NONCE;
    s2n_stack_blob(aad, is_tls13_record ? S2N_TLS13_AAD_LEN : S2N_TLS_MAX_AAD_LEN, S2N_TLS_MAX_AAD_LEN);

    /* If we aren't buffering multiple records, then the output stuffer should be empty. */
    if (!conn->multirecord_send) {
        POSIX_ENSURE(s2n_stuffer_data_available(&conn->out) == 0, S2N_ERR_RECORD_STUFFER_NEEDS_DRAINING);
    }

    /* Before we do anything, we need to figure out what the length of the
     * fragment is going to be.
     */
    uint16_t max_write_payload_size = 0;
    POSIX_GUARD_RESULT(s2n_record_max_write_payload_size(conn, &max_write_payload_size));
    const uint16_t data_bytes_to_take = MIN(to_write, max_write_payload_size);

    uint16_t extra = 0;
    POSIX_GUARD_RESULT(s2n_tls_record_overhead(conn, &extra));

    /* If we have padding to worry about, figure that out too */
    if (cipher_suite->record_alg->cipher->type == S2N_CBC) {
        block_size = cipher_suite->record_alg->cipher->io.cbc.block_size;
        if (((data_bytes_to_take + extra) % block_size)) {
            padding = block_size - ((data_bytes_to_take + extra) % block_size);
        }
    } else if (cipher_suite->record_alg->cipher->type == S2N_COMPOSITE) {
        block_size = cipher_suite->record_alg->cipher->io.comp.block_size;
    }

    if (s2n_stuffer_is_freed(&conn->out)) {
        /* If the output buffer has not been allocated yet, allocate
         * at least enough memory to hold a record with the local maximum fragment length.
         *
         * The local maximum fragment length is:
         * 1) The local default configured for new connections
         * 2) The local value set by the user via s2n_connection_prefer_throughput()
         *    or s2n_connection_prefer_low_latency()
         * 3) On the server, the minimum of the local value and the value negotiated with the
         *    client via the max_fragment_length extension
         *
         * Because this only occurs if the output buffer has not been allocated,
         * it does NOT resize existing buffers.
         */
        uint16_t max_wire_record_size = 0;
        POSIX_GUARD_RESULT(s2n_record_max_write_size(conn, max_write_payload_size, &max_wire_record_size));

        uint32_t buffer_size = MAX(conn->config->send_buffer_size_override, max_wire_record_size);
        POSIX_GUARD(s2n_stuffer_growable_alloc(&conn->out, buffer_size));
    }

    /* A record only local stuffer used to avoid tainting the conn->out stuffer or overwriting
     * previous records. It should be used to add an individual record to the out stuffer.
     */
    struct s2n_blob record_blob = { 0 };
    struct s2n_stuffer record_stuffer = { 0 };
    POSIX_GUARD(s2n_blob_init(&record_blob,
            conn->out.blob.data + conn->out.write_cursor,
            s2n_stuffer_space_remaining(&conn->out)));
    POSIX_GUARD(s2n_stuffer_init(&record_stuffer, &record_blob));

    /* Now that we know the length, start writing the record */
    uint8_t record_type = RECORD_TYPE(is_tls13_record, content_type);
    POSIX_GUARD(s2n_stuffer_write_uint8(&record_stuffer, record_type));
    POSIX_GUARD(s2n_record_write_protocol_version(conn, record_type, &record_stuffer));

    /* Compute non-payload parts of the MAC(seq num, type, proto vers, fragment length) for composite ciphers.
     * Composite "encrypt" will MAC the payload data and fill in padding.
     */
    if (cipher_suite->record_alg->cipher->type == S2N_COMPOSITE) {
        /* Only fragment length is needed for MAC, but the EVP ctrl function needs fragment length + eiv len. */
        uint16_t payload_and_eiv_len = data_bytes_to_take;
        if (conn->actual_protocol_version > S2N_TLS10) {
            payload_and_eiv_len += block_size;
        }

        /* Outputs number of extra bytes required for MAC and padding */
        int pad_and_mac_len = 0;
        POSIX_GUARD(cipher_suite->record_alg->cipher->io.comp.initial_hmac(session_key, sequence_number, content_type, conn->actual_protocol_version,
                payload_and_eiv_len, &pad_and_mac_len));
        extra += pad_and_mac_len;
    }

    /* TLS 1.3 protected record occupies one extra byte for content type */
    if (is_tls13_record) {
        extra += S2N_TLS_CONTENT_TYPE_LENGTH;
    }

    /* Rewrite the length to be the actual fragment length */
    const uint16_t actual_fragment_length = data_bytes_to_take + padding + extra;
    /* ensure actual_fragment_length + S2N_TLS_RECORD_HEADER_LENGTH <= max record length */
    const uint16_t max_record_length = is_tls13_record ? S2N_TLS13_MAXIMUM_RECORD_LENGTH : S2N_TLS_MAXIMUM_RECORD_LENGTH;
    S2N_ERROR_IF(actual_fragment_length + S2N_TLS_RECORD_HEADER_LENGTH > max_record_length, S2N_ERR_RECORD_LENGTH_TOO_LARGE);
    POSIX_GUARD(s2n_stuffer_write_uint16(&record_stuffer, actual_fragment_length));

    /* If we're AEAD, write the sequence number as an IV, and generate the AAD */
    if (cipher_suite->record_alg->cipher->type == S2N_AEAD) {
        struct s2n_stuffer iv_stuffer = { 0 };
        POSIX_GUARD(s2n_blob_init(&iv, aad_iv, sizeof(aad_iv)));
        POSIX_GUARD(s2n_stuffer_init(&iv_stuffer, &iv));

        if (cipher_suite->record_alg->flags & S2N_TLS12_AES_GCM_AEAD_NONCE) {
            /* Partially explicit nonce. See RFC 5288 Section 3 */
            POSIX_GUARD(s2n_stuffer_write_bytes(&record_stuffer, sequence_number, S2N_TLS_SEQUENCE_NUM_LEN));
            POSIX_GUARD(s2n_stuffer_write_bytes(&iv_stuffer, implicit_iv, cipher_suite->record_alg->cipher->io.aead.fixed_iv_size));
            POSIX_GUARD(s2n_stuffer_write_bytes(&iv_stuffer, sequence_number, S2N_TLS_SEQUENCE_NUM_LEN));
        } else if (cipher_suite->record_alg->flags & S2N_TLS12_CHACHA_POLY_AEAD_NONCE || is_tls13_record) {
            /* Fully implicit nonce. See RFC7905 Section 2 */
            uint8_t four_zeroes[4] = { 0 };
            POSIX_GUARD(s2n_stuffer_write_bytes(&iv_stuffer, four_zeroes, 4));
            POSIX_GUARD(s2n_stuffer_write_bytes(&iv_stuffer, sequence_number, S2N_TLS_SEQUENCE_NUM_LEN));
            for (int i = 0; i < cipher_suite->record_alg->cipher->io.aead.fixed_iv_size; i++) {
                aad_iv[i] = aad_iv[i] ^ implicit_iv[i];
            }
        } else {
            POSIX_BAIL(S2N_ERR_INVALID_NONCE_TYPE);
        }

        /* Set the IV size to the amount of data written */
        iv.size = s2n_stuffer_data_available(&iv_stuffer);
        if (is_tls13_record) {
            POSIX_GUARD_RESULT(s2n_tls13_aead_aad_init(data_bytes_to_take + S2N_TLS_CONTENT_TYPE_LENGTH, cipher_suite->record_alg->cipher->io.aead.tag_size, &aad));
        } else {
            POSIX_GUARD_RESULT(s2n_aead_aad_init(conn, sequence_number, content_type, data_bytes_to_take, &aad));
        }
    } else if (cipher_suite->record_alg->cipher->type == S2N_CBC || cipher_suite->record_alg->cipher->type == S2N_COMPOSITE) {
        POSIX_GUARD(s2n_blob_init(&iv, implicit_iv, block_size));

        /* For TLS1.1/1.2; write the IV with random data */
        if (conn->actual_protocol_version > S2N_TLS10) {
            POSIX_GUARD_RESULT(s2n_get_public_random_data(&iv));
            if (cipher_suite->record_alg->cipher->type == S2N_COMPOSITE) {
                /* Write a separate random block to the record. This will be used along with the previously generated
                 * iv blob to generate the final explicit_iv for this record.
                 *
                 * How? Openssl's AES-CBC stitched encrypt populates the first block of application data with:
                 * AES(Key, XOR(iv, initial_block))
                 *
                 * If we make initial_block a random block unrelated to random_iv, explicit IV for this record
                 * is random value based on the two random blobs we just generated:
                 * AES(Key, XOR(random_iv, explicit_iv_placeholder) == AES(Key, XOR(random_iv, random_iv2))
                 *
                 * NOTE: We can't use the same random IV blob as both the initial block and IV since it will result in:
                 * AES(Key, XOR(random_iv, random_iv)) == AES(Key, 0), which will be shared by all records in this session.
                 */
                struct s2n_blob explicit_iv_placeholder = { 0 };
                uint8_t zero_block[S2N_TLS_MAX_IV_LEN] = { 0 };
                POSIX_GUARD(s2n_blob_init(&explicit_iv_placeholder, zero_block, block_size));
                POSIX_GUARD_RESULT(s2n_get_public_random_data(&explicit_iv_placeholder));
                POSIX_GUARD(s2n_stuffer_write(&record_stuffer, &explicit_iv_placeholder));
            } else {
                /* We can write the explicit IV directly to the record for non composite CBC because
                 * s2n starts AES *after* the explicit IV.
                 */
                POSIX_GUARD(s2n_stuffer_write(&record_stuffer, &iv));
            }
        }
    }

    /* Write the plaintext data */
    POSIX_GUARD(s2n_stuffer_writev_bytes(&record_stuffer, in, in_count, offs, data_bytes_to_take));
    void *orig_write_ptr = record_stuffer.blob.data + record_stuffer.write_cursor - data_bytes_to_take;

    /* Write the MAC */
    struct s2n_blob header_blob = { 0 };
    POSIX_GUARD(s2n_blob_slice(&record_blob, &header_blob, 0, S2N_TLS_RECORD_HEADER_LENGTH));
    struct s2n_blob plaintext_blob = { 0 };
    POSIX_GUARD(s2n_blob_init(&plaintext_blob, orig_write_ptr, data_bytes_to_take));
    uint32_t mac_digest_size = 0;
    POSIX_GUARD_RESULT(s2n_record_write_mac(conn, &header_blob, &plaintext_blob, &record_stuffer, &mac_digest_size));

    /* We are done with this sequence number, so we can increment it */
    struct s2n_blob seq = { 0 };
    POSIX_GUARD(s2n_blob_init(&seq, sequence_number, S2N_TLS_SEQUENCE_NUM_LEN));
    POSIX_GUARD(s2n_increment_sequence_number(&seq));

    /* Write content type for TLS 1.3 record (RFC 8446 Section 5.2) */
    if (is_tls13_record) {
        POSIX_GUARD(s2n_stuffer_write_uint8(&record_stuffer, content_type));
    }

    if (cipher_suite->record_alg->cipher->type == S2N_CBC) {
        /* Include padding bytes, each with the value 'p', and
         * include an extra padding length byte, also with the value 'p'.
         */
        for (int i = 0; i <= padding; i++) {
            POSIX_GUARD(s2n_stuffer_write_uint8(&record_stuffer, padding));
        }
    }

    /* Rewind to rewrite/encrypt the packet */
    POSIX_GUARD(s2n_stuffer_rewrite(&record_stuffer));

    /* Skip the header */
    POSIX_GUARD(s2n_stuffer_skip_write(&record_stuffer, S2N_TLS_RECORD_HEADER_LENGTH));

    uint16_t encrypted_length = data_bytes_to_take + mac_digest_size;
    switch (cipher_suite->record_alg->cipher->type) {
        case S2N_AEAD:
            POSIX_GUARD(s2n_stuffer_skip_write(&record_stuffer, cipher_suite->record_alg->cipher->io.aead.record_iv_size));
            encrypted_length += cipher_suite->record_alg->cipher->io.aead.tag_size;
            if (is_tls13_record) {
                /* one extra byte for content type */
                encrypted_length += S2N_TLS_CONTENT_TYPE_LENGTH;
            }
            break;
        case S2N_CBC:
            if (conn->actual_protocol_version > S2N_TLS10) {
                /* Leave the IV alone and unencrypted */
                POSIX_GUARD(s2n_stuffer_skip_write(&record_stuffer, iv.size));
            }
            /* Encrypt the padding and the padding length byte too */
            encrypted_length += padding + 1;
            break;
        case S2N_COMPOSITE:
            /* Composite CBC expects a pointer starting at explicit IV: [Explicit IV | fragment | MAC | padding | padding len ]
        * extra will account for the explicit IV len(if applicable), MAC digest len, padding len + padding byte.
        */
            encrypted_length += extra;
            break;
        default:
            break;
    }

    /* Check that stuffer have enough space to write encrypted record, because raw_write cannot expand tainted stuffer */
    S2N_ERROR_IF(s2n_stuffer_space_remaining(&record_stuffer) < encrypted_length, S2N_ERR_RECORD_STUFFER_SIZE);

    /* Do the encryption */
    struct s2n_blob en = { .size = encrypted_length, .data = s2n_stuffer_raw_write(&record_stuffer, encrypted_length) };
    POSIX_GUARD(s2n_record_encrypt(conn, cipher_suite, session_key, &iv, &aad, &en, implicit_iv, block_size));

    /* Sync the out stuffer write cursor with the record stuffer. */
    POSIX_GUARD(s2n_stuffer_skip_write(&conn->out, s2n_stuffer_data_available(&record_stuffer)));

    if (conn->actual_protocol_version == S2N_TLS13 && content_type == TLS_CHANGE_CIPHER_SPEC) {
        conn->client = current_client_crypto;
        conn->server = current_server_crypto;
    }

    return data_bytes_to_take;
}