/* * 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 #include #include #include #include #include #include #include #include #include #include #include /** Session decrypt path routines **/ static int fill_request(struct aws_cryptosdk_dec_request *request, struct aws_cryptosdk_session *session) { request->alloc = session->alloc; request->alg = session->alg_props->alg_id; size_t n_keys = aws_array_list_length(&session->header.edk_list); // TODO: Make encrypted_data_keys a pointer? if (aws_cryptosdk_edk_list_init(session->alloc, &request->encrypted_data_keys)) { return AWS_OP_ERR; } request->enc_ctx = &session->header.enc_ctx; for (size_t i = 0; i < n_keys; i++) { struct aws_cryptosdk_edk edk; if (aws_array_list_get_at(&session->header.edk_list, &edk, i)) { goto UNEXPECTED_ERROR; } // Because the session header owns the EDKs, clear the allocators to avoid any unfortunate double frees edk.provider_id.allocator = NULL; edk.provider_info.allocator = NULL; edk.ciphertext.allocator = NULL; if (aws_array_list_push_back(&request->encrypted_data_keys, &edk)) { goto UNEXPECTED_ERROR; } } return AWS_OP_SUCCESS; UNEXPECTED_ERROR: aws_array_list_clean_up(&request->encrypted_data_keys); return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN); } static int derive_data_key(struct aws_cryptosdk_session *session, struct aws_cryptosdk_dec_materials *materials) { AWS_PRECONDITION(aws_cryptosdk_session_is_valid(session)); AWS_PRECONDITION(aws_cryptosdk_alg_properties_is_valid(session->alg_props)); AWS_PRECONDITION(aws_cryptosdk_dec_materials_is_valid(materials)); if (materials->unencrypted_data_key.len != session->alg_props->data_key_len) { return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN); } if (!aws_cryptosdk_priv_algorithm_allowed_for_decrypt(session->alg_props->alg_id, session->commitment_policy)) { return aws_raise_error(AWS_CRYPTOSDK_ERR_COMMITMENT_POLICY_VIOLATION); } // TODO - eliminate the struct data_key type and use the unencrypted_data_key buffer directly struct data_key data_key = { { 0 } }; memcpy(&data_key.keybuf, materials->unencrypted_data_key.buffer, materials->unencrypted_data_key.len); assert(session->alg_props->commitment_len <= sizeof(session->key_commitment_arr)); struct aws_byte_buf expected_commitment = aws_byte_buf_from_array(session->key_commitment_arr, session->alg_props->commitment_len); int rv = aws_cryptosdk_private_derive_key( session->alg_props, &session->content_key, &data_key, &expected_commitment, &session->header.message_id); if (rv != AWS_OP_SUCCESS) { return aws_raise_error(rv); } if (!aws_cryptosdk_private_commitment_eq(&expected_commitment, &session->header.alg_suite_data)) { return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } return AWS_OP_SUCCESS; } static int validate_header(struct aws_cryptosdk_session *session) { // Perform header validation size_t header_size = aws_cryptosdk_hdr_size(&session->header); size_t authtag_len = aws_cryptosdk_private_authtag_len(session->alg_props); assert(header_size == session->header_size); if (header_size == 0) { return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } if (header_size - session->header.auth_len != authtag_len) { // The authenticated length field is wrong. // XXX: This is a computed field, can this actually fail in practice? return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } struct aws_byte_buf authtag = { .buffer = session->header_copy + session->header.auth_len, .len = authtag_len }; struct aws_byte_buf headerbytebuf = { .buffer = session->header_copy, .len = session->header.auth_len }; return aws_cryptosdk_verify_header(session->alg_props, &session->content_key, &authtag, &headerbytebuf); } int aws_cryptosdk_priv_unwrap_keys(struct aws_cryptosdk_session *AWS_RESTRICT session) { struct aws_cryptosdk_dec_request request; struct aws_cryptosdk_dec_materials *materials = NULL; session->alg_props = aws_cryptosdk_alg_props(session->header.alg_id); if (!session->alg_props) { // Unknown algorithm return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } if (fill_request(&request, session)) return AWS_OP_ERR; int rv = AWS_OP_ERR; if (aws_cryptosdk_cmm_decrypt_materials(session->cmm, &materials, &request)) goto out; aws_cryptosdk_transfer_list(&session->keyring_trace, &materials->keyring_trace); session->cmm_success = true; const struct aws_cryptosdk_alg_properties *materials_alg_props = aws_cryptosdk_alg_props(materials->alg); if (!materials_alg_props) { aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_STATE); goto out; } // In AWS_CRYPTOSDK_DECRYPT_UNSIGNED mode, the operation must fail if the CMM // returns decryption materials with a signing algorithm suite if (session->mode == AWS_CRYPTOSDK_DECRYPT_UNSIGNED && materials_alg_props->signature_len) { aws_raise_error(AWS_CRYPTOSDK_ERR_DECRYPT_SIGNED_MESSAGE_NOT_ALLOWED); goto out; } if (derive_data_key(session, materials)) goto out; if (validate_header(session)) goto out; if (session->alg_props->signature_len) { if (!materials->signctx) { aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); goto out; } // Move ownership of the signature context out of the materials session->signctx = materials->signctx; materials->signctx = NULL; // Backfill the context with the header if (aws_cryptosdk_sig_update( session->signctx, aws_byte_cursor_from_array(session->header_copy, session->header_size))) { goto out; } } session->frame_seqno = 1; session->frame_size = session->header.frame_len; aws_cryptosdk_priv_session_change_state(session, ST_DECRYPT_BODY); rv = AWS_OP_SUCCESS; out: if (materials) aws_cryptosdk_dec_materials_destroy(materials); aws_array_list_clean_up(&request.encrypted_data_keys); return rv; } int aws_cryptosdk_priv_try_parse_header( struct aws_cryptosdk_session *AWS_RESTRICT session, struct aws_byte_cursor *AWS_RESTRICT input) { const uint8_t *header_start = input->ptr; int rv = aws_cryptosdk_hdr_parse(&session->header, input, session->max_encrypted_data_keys); if (rv != AWS_OP_SUCCESS) { if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { if (input->len >= session->input_size_estimate) { session->input_size_estimate = input->len + 128; if (session->input_size_estimate < input->len) { // overflow session->input_size_estimate = (size_t)-1; } } session->output_size_estimate = 0; return AWS_OP_SUCCESS; // suppress this error } return rv; } session->header_size = aws_cryptosdk_hdr_size(&session->header); if (session->header_size == 0) { return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } if ((ptrdiff_t)session->header_size != input->ptr - header_start) { return aws_raise_error(AWS_CRYPTOSDK_ERR_CRYPTO_UNKNOWN); } session->header_copy = aws_mem_acquire(session->alloc, session->header_size); if (!session->header_copy) { return aws_raise_error(AWS_ERROR_OOM); } memcpy(session->header_copy, header_start, session->header_size); aws_cryptosdk_priv_session_change_state(session, ST_UNWRAP_KEY); return aws_cryptosdk_priv_unwrap_keys(session); } int aws_cryptosdk_priv_try_decrypt_body( struct aws_cryptosdk_session *AWS_RESTRICT session, struct aws_byte_buf *AWS_RESTRICT poutput, struct aws_byte_cursor *AWS_RESTRICT pinput) { struct aws_cryptosdk_frame frame; // We'll save the original cursor state; if we don't have enough plaintext buffer we'll // need to roll back and un-consume the ciphertext. struct aws_byte_cursor input_rollback = *pinput; if (aws_cryptosdk_deserialize_frame( &frame, &session->input_size_estimate, &session->output_size_estimate, pinput, session->alg_props, session->frame_size)) { if (aws_last_error() == AWS_ERROR_SHORT_BUFFER) { // Not actually an error. We've updated the estimates, so move on. return AWS_OP_SUCCESS; } else { // Frame format was malformed. Propagate the error up the chain. return AWS_OP_ERR; } } // The frame is structurally sound. Now we just need to do some validation of its // contents and decrypt. if (session->frame_seqno != frame.sequence_number) { return aws_raise_error(AWS_CRYPTOSDK_ERR_BAD_CIPHERTEXT); } // Before we go further, do we have enough room to place the plaintext? struct aws_byte_buf output = { .buffer = 0, .len = 0, .capacity = 0, .allocator = NULL }; if (!aws_byte_buf_advance(poutput, &output, session->output_size_estimate)) { *pinput = input_rollback; // No progress due to not enough plaintext output space. return AWS_OP_SUCCESS; } // We have everything we need, try to decrypt struct aws_byte_cursor ciphertext_cursor = aws_byte_cursor_from_array(frame.ciphertext.buffer, frame.ciphertext.len); int rv = aws_cryptosdk_decrypt_body( session->alg_props, &output, &ciphertext_cursor, &session->header.message_id, frame.sequence_number, frame.iv.buffer, &session->content_key, frame.authtag.buffer, frame.type); if (rv == AWS_ERROR_SUCCESS) { session->frame_seqno++; if (session->signctx) { struct aws_byte_cursor frame = { .ptr = input_rollback.ptr, .len = pinput->ptr - input_rollback.ptr }; if (aws_cryptosdk_sig_update(session->signctx, frame)) { return AWS_OP_ERR; } } if (frame.type != FRAME_TYPE_FRAME) { aws_cryptosdk_priv_session_change_state(session, ST_CHECK_TRAILER); } return rv; } // An error was encountered; the top level loop will transition to the error state return rv; } int aws_cryptosdk_priv_check_trailer( struct aws_cryptosdk_session *AWS_RESTRICT session, struct aws_byte_cursor *AWS_RESTRICT input) { /* By the time we're here, we're not going to provide any more output. * We might need more input, and if so we'll update input_size_estimate * below. For now we'll set it to zero so that when session is * done both estimates will be zero. */ session->output_size_estimate = 0; session->input_size_estimate = 0; struct aws_byte_cursor initial_input = *input; if (session->signctx == NULL) { aws_cryptosdk_priv_session_change_state(session, ST_DONE); return AWS_OP_SUCCESS; } uint16_t sig_len = 0; struct aws_byte_cursor signature; if (!aws_byte_cursor_read_be16(input, &sig_len) || !(signature = aws_byte_cursor_advance_nospec(input, sig_len)).ptr) { // Not enough data to read the signature yet session->input_size_estimate = 2 + sig_len; *input = initial_input; return AWS_OP_SUCCESS; } // TODO: should the signature be a cursor after all? struct aws_string *signature_str = aws_string_new_from_array(session->alloc, signature.ptr, signature.len); if (!signature_str) { return AWS_OP_ERR; } int rv = aws_cryptosdk_sig_verify_finish(session->signctx, signature_str); // signctx is unconditionally freed, so avoid double free by nulling it out session->signctx = NULL; aws_string_destroy(signature_str); return rv; }