source/session_encrypt.c (296 lines of code) (raw):
/*
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
* this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <aws/common/byte_buf.h>
#include <aws/common/math.h>
#include <aws/common/string.h>
#include <aws/cryptosdk/error.h>
#include <aws/cryptosdk/list_utils.h>
#include <aws/cryptosdk/private/framefmt.h>
#include <aws/cryptosdk/private/header.h>
#include <aws/cryptosdk/private/session.h>
#include <aws/cryptosdk/session.h>
static int build_header(struct aws_cryptosdk_session *session, struct aws_cryptosdk_enc_materials *materials);
static int sign_header(struct aws_cryptosdk_session *session);
/* Session encrypt path routines */
void aws_cryptosdk_priv_encrypt_compute_body_estimate(struct aws_cryptosdk_session *session) {
if (session->state != ST_ENCRYPT_BODY) {
return;
}
/*
* We'll update the input/output estimates by simply doing a trial run of aws_cryptosdk_priv_try_encrypt_body
* with empty input/output buffers.
*/
struct aws_byte_cursor empty_input = { .ptr = (uint8_t *)"", .len = 0 };
struct aws_byte_buf empty_output = { .buffer = NULL, .len = 0, .capacity = 0 };
aws_cryptosdk_priv_try_encrypt_body(session, &empty_output, &empty_input);
}
int aws_cryptosdk_priv_try_gen_key(struct aws_cryptosdk_session *session) {
AWS_PRECONDITION(aws_cryptosdk_session_is_valid(session));
AWS_PRECONDITION(aws_cryptosdk_commitment_policy_is_valid(session->commitment_policy));
AWS_PRECONDITION(session->state == ST_GEN_KEY);
AWS_PRECONDITION(session->mode == AWS_CRYPTOSDK_ENCRYPT);
struct aws_cryptosdk_enc_request request;
struct aws_cryptosdk_enc_materials *materials = NULL;
struct data_key data_key;
int result = AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN;
request.alloc = session->alloc;
request.enc_ctx = &session->header.enc_ctx;
// The default CMM will fill this in.
request.requested_alg = 0;
request.plaintext_size = session->precise_size_known ? session->precise_size : session->size_bound;
request.commitment_policy = session->commitment_policy;
if (aws_cryptosdk_cmm_generate_enc_materials(session->cmm, &materials, &request)) {
goto rethrow;
}
// Perform basic validation of the materials generated
session->alg_props = aws_cryptosdk_alg_props(materials->alg);
if (!session->alg_props) goto out;
if (materials->unencrypted_data_key.len != session->alg_props->data_key_len) goto out;
size_t num_encrypted_data_keys = aws_array_list_length(&materials->encrypted_data_keys);
if (!num_encrypted_data_keys) goto out;
if (session->max_encrypted_data_keys && num_encrypted_data_keys > session->max_encrypted_data_keys) {
result = AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED;
goto out;
}
// We should have a signature context iff this is a signed alg suite
if (!!session->alg_props->signature_len != !!materials->signctx) goto out;
if (!aws_cryptosdk_priv_algorithm_allowed_for_encrypt(materials->alg, session->commitment_policy)) {
result = AWS_CRYPTOSDK_ERR_COMMITMENT_POLICY_VIOLATION;
goto out;
}
// Move ownership of the signature context before we go any further.
session->signctx = materials->signctx;
materials->signctx = NULL;
// TODO - eliminate the data_key type
memcpy(&data_key, materials->unencrypted_data_key.buffer, materials->unencrypted_data_key.len);
aws_cryptosdk_transfer_list(&session->keyring_trace, &materials->keyring_trace);
session->cmm_success = true;
// Generate message ID and derive the content key from the data key.
size_t message_id_len = aws_cryptosdk_private_algorithm_message_id_len(session->alg_props);
if (aws_byte_buf_init(&session->header.message_id, session->alloc, message_id_len) != AWS_OP_SUCCESS) {
goto out;
}
if (aws_cryptosdk_genrandom(session->header.message_id.buffer, message_id_len)) {
goto out;
}
session->header.message_id.len = message_id_len;
if (aws_cryptosdk_commitment_policy_encrypt_must_include_commitment(session->commitment_policy)) {
assert(session->alg_props->commitment_len <= sizeof(session->key_commitment_arr));
session->header.alg_suite_data =
aws_byte_buf_from_array(session->key_commitment_arr, session->alg_props->commitment_len);
}
if (aws_cryptosdk_private_derive_key(
session->alg_props,
&session->content_key,
&data_key,
&session->header.alg_suite_data,
&session->header.message_id)) {
goto rethrow;
}
if (build_header(session, materials)) {
goto rethrow;
}
if (sign_header(session)) {
goto rethrow;
}
result = AWS_ERROR_SUCCESS;
out:
if (result) result = aws_raise_error(result);
goto cleanup;
rethrow:
result = AWS_OP_ERR;
cleanup:
if (materials) {
aws_byte_buf_secure_zero(&materials->unencrypted_data_key);
aws_cryptosdk_enc_materials_destroy(materials);
}
aws_secure_zero(&data_key, sizeof(data_key));
return result;
}
static int build_header(struct aws_cryptosdk_session *session, struct aws_cryptosdk_enc_materials *materials) {
session->header.alg_id = session->alg_props->alg_id;
if (session->frame_size > UINT32_MAX) {
return aws_raise_error(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED);
}
session->header.frame_len = (uint32_t)session->frame_size;
// Swap the materials' EDK list for the header's. Note that these both use the session allocator
// (aws_array_list_swap_contents requires that both lists use the same allocator).
// When we clean up the materials structure we'll destroy the old EDK list.
aws_array_list_swap_contents(&session->header.edk_list, &materials->encrypted_data_keys);
// The header should have been cleared earlier, so the materials structure should have
// zero EDKs (otherwise we'd need to destroy the old EDKs as well).
assert(aws_array_list_length(&materials->encrypted_data_keys) == 0);
if (aws_byte_buf_init(&session->header.iv, session->alloc, session->alg_props->iv_len)) {
return AWS_OP_ERR;
}
aws_secure_zero(session->header.iv.buffer, session->alg_props->iv_len);
session->header.iv.len = session->header.iv.capacity;
if (aws_byte_buf_init(&session->header.auth_tag, session->alloc, session->alg_props->tag_len)) {
return AWS_OP_ERR;
}
session->header.auth_tag.len = session->header.auth_tag.capacity;
return AWS_OP_SUCCESS;
}
static int sign_header(struct aws_cryptosdk_session *session) {
AWS_PRECONDITION(aws_cryptosdk_session_is_valid(session));
AWS_PRECONDITION(aws_cryptosdk_alg_properties_is_valid(session->alg_props));
AWS_PRECONDITION(session->alg_props->impl->cipher_ctor != NULL);
AWS_PRECONDITION(session->header.iv.len <= session->alg_props->iv_len);
AWS_PRECONDITION(session->header.auth_tag.len <= session->alg_props->tag_len);
AWS_PRECONDITION(session->state == ST_GEN_KEY);
AWS_PRECONDITION(session->mode == AWS_CRYPTOSDK_ENCRYPT);
session->header_size = aws_cryptosdk_hdr_size(&session->header);
if (session->header_size == 0) {
// EDK field lengths resulted in size_t overflow
return aws_raise_error(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED);
}
if (!(session->header_copy = aws_mem_acquire(session->alloc, session->header_size))) {
return aws_raise_error(AWS_ERROR_OOM);
}
// Debug memsets - if something goes wrong below this makes it easier to
// see what happened. It also makes sure that the header is fully initialized,
// again just in case some bug doesn't overwrite them properly.
if (session->header.iv.len != 0) {
assert(session->header.iv.buffer);
memset(session->header.iv.buffer, 0x42, session->header.iv.len);
}
if (session->header.auth_tag.len != 0) {
assert(session->header.auth_tag.buffer);
memset(session->header.auth_tag.buffer, 0xDE, session->header.auth_tag.len);
}
size_t actual_size;
int rv = aws_cryptosdk_hdr_write(&session->header, &actual_size, session->header_copy, session->header_size);
if (rv) return AWS_OP_ERR;
if (actual_size != session->header_size) {
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
size_t authtag_len = aws_cryptosdk_private_authtag_len(session->alg_props);
struct aws_byte_buf to_sign = aws_byte_buf_from_array(session->header_copy, session->header_size - authtag_len);
struct aws_byte_buf authtag =
aws_byte_buf_from_array(session->header_copy + session->header_size - authtag_len, authtag_len);
rv = aws_cryptosdk_sign_header(session->alg_props, &session->content_key, &authtag, &to_sign);
if (rv) return AWS_OP_ERR;
if (session->alg_props->msg_format_version == AWS_CRYPTOSDK_HEADER_VERSION_1_0) {
if (session->header.iv.len != 0) {
assert(session->header.iv.buffer);
memcpy(session->header.iv.buffer, authtag.buffer, session->header.iv.len);
}
if (session->header.auth_tag.len != 0) {
assert(session->header.auth_tag.buffer);
memcpy(
session->header.auth_tag.buffer, authtag.buffer + session->header.iv.len, session->header.auth_tag.len);
}
} else {
if (session->header.auth_tag.len != 0) {
assert(session->header.auth_tag.buffer);
memcpy(session->header.auth_tag.buffer, authtag.buffer, session->header.auth_tag.len);
}
}
// Re-serialize the header now that we know the auth tag
rv = aws_cryptosdk_hdr_write(&session->header, &actual_size, session->header_copy, session->header_size);
if (rv) return AWS_OP_ERR;
if (actual_size != session->header_size) {
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
if (session->signctx &&
aws_cryptosdk_sig_update(
session->signctx, aws_byte_cursor_from_array(session->header_copy, session->header_size))) {
return AWS_OP_ERR;
}
session->frame_seqno = 1;
aws_cryptosdk_priv_session_change_state(session, ST_WRITE_HEADER);
// TODO - should we free the parsed header here?
return AWS_OP_SUCCESS;
}
int aws_cryptosdk_priv_try_write_header(struct aws_cryptosdk_session *session, struct aws_byte_buf *output) {
session->output_size_estimate = session->header_size;
// We'll only write the header if we have enough of an output buffer to
// write the whole thing.
// TODO - should we try to write incrementally?
if (aws_byte_buf_write(output, session->header_copy, session->header_size)) {
aws_cryptosdk_priv_session_change_state(session, ST_ENCRYPT_BODY);
}
// TODO - should we free the parsed header here?
return AWS_OP_SUCCESS;
}
int aws_cryptosdk_priv_try_encrypt_body(
struct aws_cryptosdk_session *AWS_RESTRICT session,
struct aws_byte_buf *AWS_RESTRICT poutput,
struct aws_byte_cursor *AWS_RESTRICT pinput) {
/* First, figure out how much plaintext we need. */
size_t plaintext_size;
enum aws_cryptosdk_frame_type frame_type;
if (session->frame_size) {
/* This is a framed message; is it the last frame? */
if (session->precise_size_known && session->precise_size - session->data_so_far < session->frame_size) {
plaintext_size = (size_t)(session->precise_size - session->data_so_far);
frame_type = FRAME_TYPE_FINAL;
} else {
plaintext_size = (size_t)session->frame_size;
frame_type = FRAME_TYPE_FRAME;
}
} else {
/* This is a non-framed message. We need the precise size before doing anything. */
if (!session->precise_size_known) {
session->output_size_estimate = 0;
session->input_size_estimate = 0;
return AWS_OP_SUCCESS;
}
plaintext_size = (size_t)session->precise_size;
frame_type = FRAME_TYPE_SINGLE;
}
/*
* We'll use a shadow copy of the cursors; this lets us avoid modifying the
* output if the input is too small, and vice versa.
*/
struct aws_byte_buf output = *poutput;
struct aws_byte_cursor input = *pinput;
struct aws_cryptosdk_frame frame;
size_t ciphertext_size;
frame.type = frame_type;
if (session->frame_seqno > UINT32_MAX) {
return aws_raise_error(AWS_CRYPTOSDK_ERR_LIMIT_EXCEEDED);
}
frame.sequence_number = session->frame_seqno;
int rv = aws_cryptosdk_serialize_frame(&frame, &ciphertext_size, plaintext_size, &output, session->alg_props);
session->output_size_estimate = ciphertext_size;
session->input_size_estimate = plaintext_size;
if (rv) {
if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) {
// The ciphertext buffer was too small. We've updated estimates;
// just return without doing any work.
return AWS_OP_SUCCESS;
} else {
// Some kind of validation failed?
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
}
struct aws_byte_cursor plaintext = aws_byte_cursor_advance(&input, plaintext_size);
if (!plaintext.ptr) {
// Not enough plaintext buffer space.
return AWS_OP_SUCCESS;
}
if (aws_cryptosdk_encrypt_body(
session->alg_props,
&frame.ciphertext,
&plaintext,
&session->header.message_id,
frame.sequence_number,
frame.iv.buffer,
&session->content_key,
frame.authtag.buffer,
frame.type)) {
// Something terrible happened. Clear the ciphertext buffer and error out.
aws_byte_buf_secure_zero(poutput);
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
if (session->signctx) {
// Note that the 'output' buffer contains only our ciphertext; we need to keep track of the frame
// headers as well
uint8_t *original_start = poutput->buffer + poutput->len;
uint8_t *current_end = output.buffer + output.len;
struct aws_byte_cursor to_sign = aws_byte_cursor_from_array(original_start, current_end - original_start);
if (aws_cryptosdk_sig_update(session->signctx, to_sign)) {
// Something terrible happened. Clear the ciphertext buffer and error out.
aws_secure_zero(original_start, current_end - original_start);
return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
}
// Success! Write back our input/output cursors now, and update our state.
*pinput = input;
*poutput = output;
session->data_so_far += plaintext_size;
session->frame_seqno++;
if (frame.type != FRAME_TYPE_FRAME) {
// We've written a final frame, move on to the trailer
aws_cryptosdk_priv_session_change_state(session, ST_WRITE_TRAILER);
}
return AWS_OP_SUCCESS;
}
int aws_cryptosdk_priv_write_trailer(
struct aws_cryptosdk_session *AWS_RESTRICT session, struct aws_byte_buf *AWS_RESTRICT poutput) {
/* We definitely do not need any more input at this point.
* We might need more output space, and if so we will update the
* output estimate below. For now we set it to zero so that when
* session is done both estimates will be zero.
*/
session->input_size_estimate = 0;
session->output_size_estimate = 0;
if (session->alg_props->signature_len == 0) {
aws_cryptosdk_priv_session_change_state(session, ST_DONE);
return AWS_OP_SUCCESS;
}
// The trailer frame is a 16-bit length followed by the signature.
// Since we generate the signature with a deterministic size, we know how much space we need
// ahead of time.
size_t size_needed = 2 + session->alg_props->signature_len;
if (poutput->capacity - poutput->len < size_needed) {
session->output_size_estimate = size_needed;
return AWS_OP_SUCCESS;
}
struct aws_string *signature = NULL;
int rv = aws_cryptosdk_sig_sign_finish(session->signctx, session->alloc, &signature);
// The signature context is unconditionally destroyed, so avoid double-free
session->signctx = NULL;
if (rv) {
return AWS_OP_ERR;
}
if (!aws_byte_buf_write_be16(poutput, signature->len) ||
!aws_byte_buf_write_from_whole_string(poutput, signature)) {
// Should never happen, but just in case
rv = aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN);
}
aws_string_destroy(signature);
if (rv == AWS_OP_SUCCESS) {
aws_cryptosdk_priv_session_change_state(session, ST_DONE);
}
return rv;
}