| /* |
| * |
| * Copyright 2018 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License 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 <grpc/support/port_platform.h> |
| |
| #include "src/core/tsi/alts/frame_protector/alts_frame_protector.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <grpc/support/alloc.h> |
| #include <grpc/support/log.h> |
| |
| #include "src/core/lib/gpr/useful.h" |
| #include "src/core/tsi/alts/crypt/gsec.h" |
| #include "src/core/tsi/alts/frame_protector/alts_crypter.h" |
| #include "src/core/tsi/alts/frame_protector/frame_handler.h" |
| #include "src/core/tsi/transport_security.h" |
| |
| constexpr size_t kMinFrameLength = 1024; |
| constexpr size_t kDefaultFrameLength = 16 * 1024; |
| constexpr size_t kMaxFrameLength = 1024 * 1024; |
| |
| // Limit k on number of frames such that at most 2^(8 * k) frames can be sent. |
| constexpr size_t kAltsRecordProtocolRekeyFrameLimit = 8; |
| constexpr size_t kAltsRecordProtocolFrameLimit = 5; |
| |
| /* Main struct for alts_frame_protector. */ |
| struct alts_frame_protector { |
| tsi_frame_protector base; |
| alts_crypter* seal_crypter; |
| alts_crypter* unseal_crypter; |
| alts_frame_writer* writer; |
| alts_frame_reader* reader; |
| unsigned char* in_place_protect_buffer; |
| unsigned char* in_place_unprotect_buffer; |
| size_t in_place_protect_bytes_buffered; |
| size_t in_place_unprotect_bytes_processed; |
| size_t max_protected_frame_size; |
| size_t max_unprotected_frame_size; |
| size_t overhead_length; |
| size_t counter_overflow; |
| }; |
| |
| static tsi_result seal(alts_frame_protector* impl) { |
| char* error_details = nullptr; |
| size_t output_size = 0; |
| grpc_status_code status = alts_crypter_process_in_place( |
| impl->seal_crypter, impl->in_place_protect_buffer, |
| impl->max_protected_frame_size, impl->in_place_protect_bytes_buffered, |
| &output_size, &error_details); |
| impl->in_place_protect_bytes_buffered = output_size; |
| if (status != GRPC_STATUS_OK) { |
| gpr_log(GPR_ERROR, "%s", error_details); |
| gpr_free(error_details); |
| return TSI_INTERNAL_ERROR; |
| } |
| return TSI_OK; |
| } |
| |
| static size_t max_encrypted_payload_bytes(alts_frame_protector* impl) { |
| return impl->max_protected_frame_size - kFrameHeaderSize; |
| } |
| |
| static tsi_result alts_protect_flush(tsi_frame_protector* self, |
| unsigned char* protected_output_frames, |
| size_t* protected_output_frames_size, |
| size_t* still_pending_size) { |
| if (self == nullptr || protected_output_frames == nullptr || |
| protected_output_frames_size == nullptr || |
| still_pending_size == nullptr) { |
| gpr_log(GPR_ERROR, "Invalid nullptr arguments to alts_protect_flush()."); |
| return TSI_INVALID_ARGUMENT; |
| } |
| alts_frame_protector* impl = reinterpret_cast<alts_frame_protector*>(self); |
| /** |
| * If there's nothing to flush (i.e., in_place_protect_buffer is empty), |
| * we're done. |
| */ |
| if (impl->in_place_protect_bytes_buffered == 0) { |
| *protected_output_frames_size = 0; |
| *still_pending_size = 0; |
| return TSI_OK; |
| } |
| /** |
| * If a new frame can start being processed, we encrypt the payload and reset |
| * the frame writer to point to in_place_protect_buffer that holds the newly |
| * sealed frame. |
| */ |
| if (alts_is_frame_writer_done(impl->writer)) { |
| tsi_result result = seal(impl); |
| if (result != TSI_OK) { |
| return result; |
| } |
| if (!alts_reset_frame_writer(impl->writer, impl->in_place_protect_buffer, |
| impl->in_place_protect_bytes_buffered)) { |
| gpr_log(GPR_ERROR, "Couldn't reset frame writer."); |
| return TSI_INTERNAL_ERROR; |
| } |
| } |
| /** |
| * Write the sealed frame as much as possible to protected_output_frames. It's |
| * possible a frame will not be written out completely by a single flush |
| * (i.e., still_pending_size != 0), in which case the flush should be called |
| * iteratively until a complete frame has been written out. |
| */ |
| size_t written_frame_bytes = *protected_output_frames_size; |
| if (!alts_write_frame_bytes(impl->writer, protected_output_frames, |
| &written_frame_bytes)) { |
| gpr_log(GPR_ERROR, "Couldn't write frame bytes."); |
| return TSI_INTERNAL_ERROR; |
| } |
| *protected_output_frames_size = written_frame_bytes; |
| *still_pending_size = alts_get_num_writer_bytes_remaining(impl->writer); |
| /** |
| * If the current frame has been finished processing (i.e., sealed and written |
| * out completely), we empty in_place_protect_buffer. |
| */ |
| if (alts_is_frame_writer_done(impl->writer)) { |
| impl->in_place_protect_bytes_buffered = 0; |
| } |
| return TSI_OK; |
| } |
| |
| static tsi_result alts_protect(tsi_frame_protector* self, |
| const unsigned char* unprotected_bytes, |
| size_t* unprotected_bytes_size, |
| unsigned char* protected_output_frames, |
| size_t* protected_output_frames_size) { |
| if (self == nullptr || unprotected_bytes == nullptr || |
| unprotected_bytes_size == nullptr || protected_output_frames == nullptr || |
| protected_output_frames_size == nullptr) { |
| gpr_log(GPR_ERROR, "Invalid nullptr arguments to alts_protect()."); |
| return TSI_INVALID_ARGUMENT; |
| } |
| alts_frame_protector* impl = reinterpret_cast<alts_frame_protector*>(self); |
| |
| /** |
| * If more payload can be buffered, we buffer it as much as possible to |
| * in_place_protect_buffer. |
| */ |
| if (impl->in_place_protect_bytes_buffered + impl->overhead_length < |
| max_encrypted_payload_bytes(impl)) { |
| size_t bytes_to_buffer = GPR_MIN(*unprotected_bytes_size, |
| max_encrypted_payload_bytes(impl) - |
| impl->in_place_protect_bytes_buffered - |
| impl->overhead_length); |
| *unprotected_bytes_size = bytes_to_buffer; |
| if (bytes_to_buffer > 0) { |
| memcpy( |
| impl->in_place_protect_buffer + impl->in_place_protect_bytes_buffered, |
| unprotected_bytes, bytes_to_buffer); |
| impl->in_place_protect_bytes_buffered += bytes_to_buffer; |
| } |
| } else { |
| *unprotected_bytes_size = 0; |
| } |
| /** |
| * If a full frame has been buffered, we output it. If the first condition |
| * holds, then there exists an unencrypted full frame. If the second |
| * condition holds, then there exists a full frame that has already been |
| * encrypted. |
| */ |
| if (max_encrypted_payload_bytes(impl) == |
| impl->in_place_protect_bytes_buffered + impl->overhead_length || |
| max_encrypted_payload_bytes(impl) == |
| impl->in_place_protect_bytes_buffered) { |
| size_t still_pending_size = 0; |
| return alts_protect_flush(self, protected_output_frames, |
| protected_output_frames_size, |
| &still_pending_size); |
| } else { |
| *protected_output_frames_size = 0; |
| return TSI_OK; |
| } |
| } |
| |
| static tsi_result unseal(alts_frame_protector* impl) { |
| char* error_details = nullptr; |
| size_t output_size = 0; |
| grpc_status_code status = alts_crypter_process_in_place( |
| impl->unseal_crypter, impl->in_place_unprotect_buffer, |
| impl->max_unprotected_frame_size, |
| alts_get_output_bytes_read(impl->reader), &output_size, &error_details); |
| if (status != GRPC_STATUS_OK) { |
| gpr_log(GPR_ERROR, "%s", error_details); |
| gpr_free(error_details); |
| return TSI_DATA_CORRUPTED; |
| } |
| return TSI_OK; |
| } |
| |
| static void ensure_buffer_size(alts_frame_protector* impl) { |
| if (!alts_has_read_frame_length(impl->reader)) { |
| return; |
| } |
| size_t buffer_space_remaining = impl->max_unprotected_frame_size - |
| alts_get_output_bytes_read(impl->reader); |
| /** |
| * Check if we need to resize in_place_unprotect_buffer in order to hold |
| * remaining bytes of a full frame. |
| */ |
| if (buffer_space_remaining < alts_get_reader_bytes_remaining(impl->reader)) { |
| size_t buffer_len = alts_get_output_bytes_read(impl->reader) + |
| alts_get_reader_bytes_remaining(impl->reader); |
| unsigned char* buffer = static_cast<unsigned char*>(gpr_malloc(buffer_len)); |
| memcpy(buffer, impl->in_place_unprotect_buffer, |
| alts_get_output_bytes_read(impl->reader)); |
| impl->max_unprotected_frame_size = buffer_len; |
| gpr_free(impl->in_place_unprotect_buffer); |
| impl->in_place_unprotect_buffer = buffer; |
| alts_reset_reader_output_buffer( |
| impl->reader, buffer + alts_get_output_bytes_read(impl->reader)); |
| } |
| } |
| |
| static tsi_result alts_unprotect(tsi_frame_protector* self, |
| const unsigned char* protected_frames_bytes, |
| size_t* protected_frames_bytes_size, |
| unsigned char* unprotected_bytes, |
| size_t* unprotected_bytes_size) { |
| if (self == nullptr || protected_frames_bytes == nullptr || |
| protected_frames_bytes_size == nullptr || unprotected_bytes == nullptr || |
| unprotected_bytes_size == nullptr) { |
| gpr_log(GPR_ERROR, "Invalid nullptr arguments to alts_unprotect()."); |
| return TSI_INVALID_ARGUMENT; |
| } |
| alts_frame_protector* impl = reinterpret_cast<alts_frame_protector*>(self); |
| /** |
| * If a new frame can start being processed, we reset the frame reader to |
| * point to in_place_unprotect_buffer that will be used to hold deframed |
| * result. |
| */ |
| if (alts_is_frame_reader_done(impl->reader) && |
| ((alts_get_output_buffer(impl->reader) == nullptr) || |
| (alts_get_output_bytes_read(impl->reader) == |
| impl->in_place_unprotect_bytes_processed + impl->overhead_length))) { |
| if (!alts_reset_frame_reader(impl->reader, |
| impl->in_place_unprotect_buffer)) { |
| gpr_log(GPR_ERROR, "Couldn't reset frame reader."); |
| return TSI_INTERNAL_ERROR; |
| } |
| impl->in_place_unprotect_bytes_processed = 0; |
| } |
| /** |
| * If a full frame has not yet been read, we read more bytes from |
| * protected_frames_bytes until a full frame has been read. We also need to |
| * make sure in_place_unprotect_buffer is large enough to hold a complete |
| * frame. |
| */ |
| if (!alts_is_frame_reader_done(impl->reader)) { |
| ensure_buffer_size(impl); |
| *protected_frames_bytes_size = |
| GPR_MIN(impl->max_unprotected_frame_size - |
| alts_get_output_bytes_read(impl->reader), |
| *protected_frames_bytes_size); |
| size_t read_frames_bytes_size = *protected_frames_bytes_size; |
| if (!alts_read_frame_bytes(impl->reader, protected_frames_bytes, |
| &read_frames_bytes_size)) { |
| gpr_log(GPR_ERROR, "Failed to process frame."); |
| return TSI_INTERNAL_ERROR; |
| } |
| *protected_frames_bytes_size = read_frames_bytes_size; |
| } else { |
| *protected_frames_bytes_size = 0; |
| } |
| /** |
| * If a full frame has been read, we unseal it, and write out the |
| * deframed result to unprotected_bytes. |
| */ |
| if (alts_is_frame_reader_done(impl->reader)) { |
| if (impl->in_place_unprotect_bytes_processed == 0) { |
| tsi_result result = unseal(impl); |
| if (result != TSI_OK) { |
| return result; |
| } |
| } |
| size_t bytes_to_write = GPR_MIN( |
| *unprotected_bytes_size, alts_get_output_bytes_read(impl->reader) - |
| impl->in_place_unprotect_bytes_processed - |
| impl->overhead_length); |
| if (bytes_to_write > 0) { |
| memcpy(unprotected_bytes, |
| impl->in_place_unprotect_buffer + |
| impl->in_place_unprotect_bytes_processed, |
| bytes_to_write); |
| } |
| *unprotected_bytes_size = bytes_to_write; |
| impl->in_place_unprotect_bytes_processed += bytes_to_write; |
| return TSI_OK; |
| } else { |
| *unprotected_bytes_size = 0; |
| return TSI_OK; |
| } |
| } |
| |
| static void alts_destroy(tsi_frame_protector* self) { |
| alts_frame_protector* impl = reinterpret_cast<alts_frame_protector*>(self); |
| if (impl != nullptr) { |
| alts_crypter_destroy(impl->seal_crypter); |
| alts_crypter_destroy(impl->unseal_crypter); |
| gpr_free(impl->in_place_protect_buffer); |
| gpr_free(impl->in_place_unprotect_buffer); |
| alts_destroy_frame_writer(impl->writer); |
| alts_destroy_frame_reader(impl->reader); |
| gpr_free(impl); |
| } |
| } |
| |
| static const tsi_frame_protector_vtable alts_frame_protector_vtable = { |
| alts_protect, alts_protect_flush, alts_unprotect, alts_destroy}; |
| |
| static grpc_status_code create_alts_crypters(const uint8_t* key, |
| size_t key_size, bool is_client, |
| bool is_rekey, |
| alts_frame_protector* impl, |
| char** error_details) { |
| grpc_status_code status; |
| gsec_aead_crypter* aead_crypter_seal = nullptr; |
| gsec_aead_crypter* aead_crypter_unseal = nullptr; |
| status = gsec_aes_gcm_aead_crypter_create(key, key_size, kAesGcmNonceLength, |
| kAesGcmTagLength, is_rekey, |
| &aead_crypter_seal, error_details); |
| if (status != GRPC_STATUS_OK) { |
| return status; |
| } |
| status = gsec_aes_gcm_aead_crypter_create( |
| key, key_size, kAesGcmNonceLength, kAesGcmTagLength, is_rekey, |
| &aead_crypter_unseal, error_details); |
| if (status != GRPC_STATUS_OK) { |
| return status; |
| } |
| size_t overflow_size = is_rekey ? kAltsRecordProtocolRekeyFrameLimit |
| : kAltsRecordProtocolFrameLimit; |
| status = alts_seal_crypter_create(aead_crypter_seal, is_client, overflow_size, |
| &impl->seal_crypter, error_details); |
| if (status != GRPC_STATUS_OK) { |
| return status; |
| } |
| status = |
| alts_unseal_crypter_create(aead_crypter_unseal, is_client, overflow_size, |
| &impl->unseal_crypter, error_details); |
| return status; |
| } |
| |
| tsi_result alts_create_frame_protector(const uint8_t* key, size_t key_size, |
| bool is_client, bool is_rekey, |
| size_t* max_protected_frame_size, |
| tsi_frame_protector** self) { |
| if (key == nullptr || self == nullptr) { |
| gpr_log(GPR_ERROR, |
| "Invalid nullptr arguments to alts_create_frame_protector()."); |
| return TSI_INTERNAL_ERROR; |
| } |
| char* error_details = nullptr; |
| alts_frame_protector* impl = |
| static_cast<alts_frame_protector*>(gpr_zalloc(sizeof(*impl))); |
| grpc_status_code status = create_alts_crypters( |
| key, key_size, is_client, is_rekey, impl, &error_details); |
| if (status != GRPC_STATUS_OK) { |
| gpr_log(GPR_ERROR, "Failed to create ALTS crypters, %s.", error_details); |
| gpr_free(error_details); |
| return TSI_INTERNAL_ERROR; |
| } |
| /** |
| * Set maximum frame size to be used by a frame protector. If it is nullptr, a |
| * default frame size will be used. Otherwise, the provided frame size will be |
| * adjusted (if not falling into a valid frame range) and used. |
| */ |
| size_t max_protected_frame_size_to_set = kDefaultFrameLength; |
| if (max_protected_frame_size != nullptr) { |
| *max_protected_frame_size = |
| GPR_MIN(*max_protected_frame_size, kMaxFrameLength); |
| *max_protected_frame_size = |
| GPR_MAX(*max_protected_frame_size, kMinFrameLength); |
| max_protected_frame_size_to_set = *max_protected_frame_size; |
| } |
| impl->max_protected_frame_size = max_protected_frame_size_to_set; |
| impl->max_unprotected_frame_size = max_protected_frame_size_to_set; |
| impl->in_place_protect_bytes_buffered = 0; |
| impl->in_place_unprotect_bytes_processed = 0; |
| impl->in_place_protect_buffer = static_cast<unsigned char*>( |
| gpr_malloc(sizeof(unsigned char) * max_protected_frame_size_to_set)); |
| impl->in_place_unprotect_buffer = static_cast<unsigned char*>( |
| gpr_malloc(sizeof(unsigned char) * max_protected_frame_size_to_set)); |
| impl->overhead_length = alts_crypter_num_overhead_bytes(impl->seal_crypter); |
| impl->writer = alts_create_frame_writer(); |
| impl->reader = alts_create_frame_reader(); |
| impl->base.vtable = &alts_frame_protector_vtable; |
| *self = &impl->base; |
| return TSI_OK; |
| } |