diff options
Diffstat (limited to 'src/core/tsi/alts/frame_protector')
12 files changed, 1889 insertions, 0 deletions
diff --git a/src/core/tsi/alts/frame_protector/alts_counter.cc b/src/core/tsi/alts/frame_protector/alts_counter.cc new file mode 100644 index 0000000000..de163e3e08 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_counter.cc @@ -0,0 +1,118 @@ +/* + * + * 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_counter.h" + +#include <string.h> + +#include <grpc/support/alloc.h> + +static void maybe_copy_error_msg(const char* src, char** dst) { + if (dst != nullptr && src != nullptr) { + *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1)); + memcpy(*dst, src, strlen(src) + 1); + } +} + +grpc_status_code alts_counter_create(bool is_client, size_t counter_size, + size_t overflow_size, + alts_counter** crypter_counter, + char** error_details) { + /* Perform input sanity check. */ + if (counter_size == 0) { + const char error_msg[] = "counter_size is invalid."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + if (overflow_size == 0 || overflow_size >= counter_size) { + const char error_msg[] = "overflow_size is invalid."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + if (crypter_counter == nullptr) { + const char error_msg[] = "crypter_counter is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + *crypter_counter = + static_cast<alts_counter*>(gpr_malloc(sizeof(**crypter_counter))); + (*crypter_counter)->size = counter_size; + (*crypter_counter)->overflow_size = overflow_size; + (*crypter_counter)->counter = + static_cast<unsigned char*>(gpr_zalloc(counter_size)); + if (is_client) { + ((*crypter_counter)->counter)[counter_size - 1] = 0x80; + } + return GRPC_STATUS_OK; +} + +grpc_status_code alts_counter_increment(alts_counter* crypter_counter, + bool* is_overflow, + char** error_details) { + /* Perform input sanity check. */ + if (crypter_counter == nullptr) { + const char error_msg[] = "crypter_counter is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + if (is_overflow == nullptr) { + const char error_msg[] = "is_overflow is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + /* Increment the internal counter. */ + size_t i = 0; + for (; i < crypter_counter->overflow_size; i++) { + (crypter_counter->counter)[i]++; + if ((crypter_counter->counter)[i] != 0x00) { + break; + } + } + /** + * If the lower overflow_size bytes are all zero, the counter has overflowed. + */ + if (i == crypter_counter->overflow_size) { + *is_overflow = true; + return GRPC_STATUS_FAILED_PRECONDITION; + } + *is_overflow = false; + return GRPC_STATUS_OK; +} + +size_t alts_counter_get_size(alts_counter* crypter_counter) { + if (crypter_counter == nullptr) { + return 0; + } + return crypter_counter->size; +} + +unsigned char* alts_counter_get_counter(alts_counter* crypter_counter) { + if (crypter_counter == nullptr) { + return nullptr; + } + return crypter_counter->counter; +} + +void alts_counter_destroy(alts_counter* crypter_counter) { + if (crypter_counter != nullptr) { + gpr_free(crypter_counter->counter); + gpr_free(crypter_counter); + } +} diff --git a/src/core/tsi/alts/frame_protector/alts_counter.h b/src/core/tsi/alts/frame_protector/alts_counter.h new file mode 100644 index 0000000000..d705638fa8 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_counter.h @@ -0,0 +1,98 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_COUNTER_H +#define GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_COUNTER_H + +#include <grpc/support/port_platform.h> + +#include <stdbool.h> +#include <stdlib.h> + +#include <grpc/grpc.h> + +/* Main struct for a crypter counter managed within seal/unseal operations. */ +typedef struct alts_counter { + size_t size; + size_t overflow_size; + unsigned char* counter; +} alts_counter; + +/** + * This method creates and initializes an alts_counter instance. + * + * - is_client: a flag indicating if the alts_counter instance will be used + * at client (is_client = true) or server (is_client = false) side. + * - counter_size: size of buffer holding the counter value. + * - overflow_size: overflow size in bytes. The counter instance can be used + * to produce at most 2^(overflow_size*8) frames. + * - crypter_counter: an alts_counter instance to be returned from the method. + * - error_details: a buffer containing an error message if the method does not + * function correctly. It is legal to pass nullptr into error_details and + * otherwise, the parameter should be freed with gpr_free. + * + * On success, the method returns GRPC_STATUS_OK. Otherwise, + * it returns an error status code along with its details specified in + * error_details (if error_details is not nullptr). + */ +grpc_status_code alts_counter_create(bool is_client, size_t counter_size, + size_t overflow_size, + alts_counter** crypter_counter, + char** error_details); + +/** + * This method increments the internal counter. + * + * - crypter_counter: an alts_counter instance. + * - is_overflow: after incrementing the internal counter, if an overflow + * occurs, is_overflow is set to true, and no further calls to + * alts_counter_increment() should be made. Otherwise, is_overflow is set to + * false. + * - error_details: a buffer containing an error message if the method does not + * function correctly. It is legal to pass nullptr into error_details and + * otherwise, the parameter should be freed with gpr_free. + * + * On success, the method returns GRPC_STATUS_OK. Otherwise, + * it returns an error status code along with its details specified in + * error_details (if error_details is not nullptr). + */ +grpc_status_code alts_counter_increment(alts_counter* crypter_counter, + bool* is_overflow, + char** error_details); + +/** + * This method returns the size of counter buffer. + * + * - crypter_counter: an alts_counter instance. + */ +size_t alts_counter_get_size(alts_counter* crypter_counter); + +/** + * This method returns the counter buffer. + * + * - crypter_counter: an alts_counter instance. + */ +unsigned char* alts_counter_get_counter(alts_counter* crypter_counter); + +/** + * This method de-allocates all memory allocated to an alts_coutner instance. + * - crypter_counter: an alts_counter instance. + */ +void alts_counter_destroy(alts_counter* crypter_counter); + +#endif /* GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_COUNTER_H */ diff --git a/src/core/tsi/alts/frame_protector/alts_crypter.cc b/src/core/tsi/alts/frame_protector/alts_crypter.cc new file mode 100644 index 0000000000..56f0512186 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_crypter.cc @@ -0,0 +1,66 @@ +/* + * + * 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_crypter.h" + +#include <string.h> + +#include <grpc/support/alloc.h> + +static void maybe_copy_error_msg(const char* src, char** dst) { + if (dst != nullptr && src != nullptr) { + *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1)); + memcpy(*dst, src, strlen(src) + 1); + } +} + +grpc_status_code alts_crypter_process_in_place( + alts_crypter* crypter, unsigned char* data, size_t data_allocated_size, + size_t data_size, size_t* output_size, char** error_details) { + if (crypter != nullptr && crypter->vtable != nullptr && + crypter->vtable->process_in_place != nullptr) { + return crypter->vtable->process_in_place(crypter, data, data_allocated_size, + data_size, output_size, + error_details); + } + /* An error occurred. */ + const char error_msg[] = + "crypter or crypter->vtable has not been initialized properly."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; +} + +size_t alts_crypter_num_overhead_bytes(const alts_crypter* crypter) { + if (crypter != nullptr && crypter->vtable != nullptr && + crypter->vtable->num_overhead_bytes != nullptr) { + return crypter->vtable->num_overhead_bytes(crypter); + } + /* An error occurred. */ + return 0; +} + +void alts_crypter_destroy(alts_crypter* crypter) { + if (crypter != nullptr) { + if (crypter->vtable != nullptr && crypter->vtable->destruct != nullptr) { + crypter->vtable->destruct(crypter); + } + gpr_free(crypter); + } +} diff --git a/src/core/tsi/alts/frame_protector/alts_crypter.h b/src/core/tsi/alts/frame_protector/alts_crypter.h new file mode 100644 index 0000000000..3140778f4f --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_crypter.h @@ -0,0 +1,255 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_CRYPTER_H +#define GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_CRYPTER_H + +#include <grpc/support/port_platform.h> + +#include <stdbool.h> +#include <string.h> + +#include <grpc/grpc.h> + +#include "src/core/tsi/alts/crypt/gsec.h" + +/** + * An alts_crypter interface for an ALTS record protocol providing + * seal/unseal functionality. The interface is thread-compatible. + */ + +typedef struct alts_crypter alts_crypter; + +/** + * A typical usage of the interface would be + *------------------------------------------------------------------------------ + * // Perform a seal operation. We assume the gsec_aead_crypter instance - + * // client_aead_crypter is created beforehand with a 16-byte key and 12-byte + * // nonce length. + * + * alts_crypter* client = nullptr; + * char* client_error_in_creation = nullptr; + * unsigned char* data = nullptr; + * grpc_status_code client_status = + * alts_seal_crypter_create(client_aead_crypter, 1, 5, &client, + * &client_error_in_creation); + * if (client_status == GRPC_STATUS_OK) { + * size_t data_size = 100; + * size_t num_overhead_bytes = alts_crypter_num_overhead_bytes(client); + * size_t data_allocated_size = data_size + num_overhead_bytes; + * data = gpr_malloc(data_allocated_size); + * char* client_error_in_seal = nullptr; + * // Client performs a seal operation. + * client_status = alts_crypter_process_in_place(client, data, + * data_allocated_size, + * &data_size, + * &client_error_in_seal); + * if (client_status != GRPC_STATUS_OK) { + * fprintf(stderr, "seal operation failed with error code:" + * "%d, message: %s\n", client_status, + * client_error_in_seal); + * } + * gpr_free(client_error_in_seal); + * } else { + * fprintf(stderr, "alts_crypter instance creation failed with error" + * "code: %d, message: %s\n", client_status, + * client_error_in_creation); + * } + * + * ... + * + * gpr_free(client_error_in_creation); + * alts_crypter_destroy(client); + * + * ... + * + * // Perform an unseal operation. We assume the gsec_aead_crypter instance - + * // server_aead_crypter is created beforehand with a 16-byte key and 12-byte + * // nonce length. The key used in the creation of gsec_aead_crypter instances + * // at server and client sides should be identical. + * + * alts_crypter* server = nullptr; + * char* server_error_in_creation = nullptr; + * grpc_status_code server_status = + * alts_unseal_crypter_create(server_aead_crypter, 0, 5, &server, + * &server_error_in_creation); + * if (server_status == GRPC_STATUS_OK) { + * size_t num_overhead_bytes = alts_crypter_num_overhead_bytes(server); + * size_t data_size = 100 + num_overhead_bytes; + * size_t data_allocated_size = data_size; + * char* server_error_in_unseal = nullptr; + * // Server performs an unseal operation. + * server_status = alts_crypter_process_in_place(server, data, + * data_allocated_size, + * &data_size, + * &server_error_in_unseal); + * if (server_status != GRPC_STATUS_OK) { + * fprintf(stderr, "unseal operation failed with error code:" + * "%d, message: %s\n", server_status, + * server_error_in_unseal); + * } + * gpr_free(server_error_in_unseal); + * } else { + * fprintf(stderr, "alts_crypter instance creation failed with error" + * "code: %d, message: %s\n", server_status, + * server_error_in_creation); + * } + * + * ... + * + * gpr_free(data); + * gpr_free(server_error_in_creation); + * alts_crypter_destroy(server); + * + * ... + *------------------------------------------------------------------------------ + */ + +/* V-table for alts_crypter operations */ +typedef struct alts_crypter_vtable { + size_t (*num_overhead_bytes)(const alts_crypter* crypter); + grpc_status_code (*process_in_place)(alts_crypter* crypter, + unsigned char* data, + size_t data_allocated_size, + size_t data_size, size_t* output_size, + char** error_details); + void (*destruct)(alts_crypter* crypter); +} alts_crypter_vtable; + +/* Main struct for alts_crypter interface */ +struct alts_crypter { + const alts_crypter_vtable* vtable; +}; + +/** + * This method gets the number of overhead bytes needed for sealing data that + * is the difference in size between the protected and raw data. The counter + * value used in a seal or unseal operation is locally maintained (not sent or + * received from the other peer) and therefore, will not be counted as part of + * overhead bytes. + * + * - crypter: an alts_crypter instance. + * + * On success, the method returns the number of overhead bytes. Otherwise, it + * returns zero. + * + */ +size_t alts_crypter_num_overhead_bytes(const alts_crypter* crypter); + +/** + * This method performs either a seal or an unseal operation depending on the + * alts_crypter instance - crypter passed to the method. If the crypter is + * an instance implementing a seal operation, the method will perform a seal + * operation. That is, it seals raw data and stores the result in-place, and the + * memory allocated for data must be at least data_length + + * alts_crypter_num_overhead_bytes(). If the crypter is an instance + * implementing an unseal operation, the method will perform an unseal + * operation. That is, it unseals protected data and stores the result in-place. + * The size of unsealed data will be data_length - + * alts_crypter_num_overhead_bytes(). Integrity tag will be verified during + * the unseal operation, and if verification fails, the data will be wiped. + * The counters used in both seal and unseal operations are managed internally. + * + * - crypter: an alts_crypter instance. + * - data: if the method performs a seal operation, the data represents raw data + * that needs to be sealed. It also plays the role of buffer to hold the + * protected data as a result of seal. If the method performs an unseal + * operation, the data represents protected data that needs to be unsealed. It + * also plays the role of buffer to hold raw data as a result of unseal. + * - data_allocated_size: the size of data buffer. The parameter is used to + * check whether the result of either seal or unseal can be safely written to + * the data buffer. + * - data_size: if the method performs a seal operation, data_size + * represents the size of raw data that needs to be sealed, and if the method + * performs an unseal operation, data_size represents the size of protected + * data that needs to be unsealed. + * - output_size: size of data written to the data buffer after a seal or an + * unseal operation. + * - error_details: a buffer containing an error message if the method does not + * function correctly. It is legal to pass nullptr into error_details and + * otherwise, the parameter should be freed with gpr_free. + * + * On success, the method returns GRPC_STATUS_OK. Otherwise, + * it returns an error status code along with its details specified in + * error_details (if error_details is not nullptr). + */ +grpc_status_code alts_crypter_process_in_place( + alts_crypter* crypter, unsigned char* data, size_t data_allocated_size, + size_t data_size, size_t* output_size, char** error_details); + +/** + * This method creates an alts_crypter instance to be used to perform a seal + * operation, given a gsec_aead_crypter instance and a flag indicating if the + * created instance will be used at the client or server side. It takes + * ownership of gsec_aead_crypter instance. + * + * - gc: a gsec_aead_crypter instance used to perform AEAD encryption. + * - is_client: a flag indicating if the alts_crypter instance will be + * used at the client (is_client = true) or server (is_client = + * false) side. + * - overflow_size: overflow size of counter in bytes. + * - crypter: an alts_crypter instance to be returned from the method. + * - error_details: a buffer containing an error message if the method does + * not function correctly. It is legal to pass nullptr into error_details, and + * otherwise, the parameter should be freed with gpr_free. + * + * On success of creation, the method returns GRPC_STATUS_OK. + * Otherwise, it returns an error status code along with its details specified + * in error_details (if error_details is not nullptr). + */ +grpc_status_code alts_seal_crypter_create(gsec_aead_crypter* gc, bool is_client, + size_t overflow_size, + alts_crypter** crypter, + char** error_details); + +/** + * This method creates an alts_crypter instance used to perform an unseal + * operation, given a gsec_aead_crypter instance and a flag indicating if the + * created instance will be used at the client or server side. It takes + * ownership of gsec_aead_crypter instance. + * + * - gc: a gsec_aead_crypter instance used to perform AEAD decryption. + * - is_client: a flag indicating if the alts_crypter instance will be + * used at the client (is_client = true) or server (is_client = + * false) side. + * - overflow_size: overflow size of counter in bytes. + * - crypter: an alts_crypter instance to be returned from the method. + * - error_details: a buffer containing an error message if the method does + * not function correctly. It is legal to pass nullptr into error_details, and + * otherwise, the parameter should be freed with gpr_free. + * + * On success of creation, the method returns GRPC_STATUS_OK. + * Otherwise, it returns an error status code along with its details specified + * in error_details (if error_details is not nullptr). + */ +grpc_status_code alts_unseal_crypter_create(gsec_aead_crypter* gc, + bool is_client, + size_t overflow_size, + alts_crypter** crypter, + char** error_details); + +/** + * This method destroys an alts_crypter instance by de-allocating all of its + * occupied memory. A gsec_aead_crypter instance passed in at alts_crypter + * instance creation time will be destroyed in this method. + * + * - crypter: an alts_crypter instance. + */ +void alts_crypter_destroy(alts_crypter* crypter); + +#endif /* GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_CRYPTER_H */ diff --git a/src/core/tsi/alts/frame_protector/alts_frame_protector.cc b/src/core/tsi/alts/frame_protector/alts_frame_protector.cc new file mode 100644 index 0000000000..bfa0b7a720 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_frame_protector.cc @@ -0,0 +1,407 @@ +/* + * + * 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; +} diff --git a/src/core/tsi/alts/frame_protector/alts_frame_protector.h b/src/core/tsi/alts/frame_protector/alts_frame_protector.h new file mode 100644 index 0000000000..321bffaed8 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_frame_protector.h @@ -0,0 +1,55 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_FRAME_PROTECTOR_H +#define GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_FRAME_PROTECTOR_H + +#include <grpc/support/port_platform.h> + +#include <stdbool.h> + +#include "src/core/tsi/transport_security_interface.h" + +typedef struct alts_frame_protector alts_frame_protector; + +/** + * TODO: Add a parameter to the interface to support the use of + * different record protocols within a frame protector. + * + * This method creates a frame protector. + * + * - key: a symmetric key used to seal/unseal frames. + * - key_size: the size of symmetric key. + * - is_client: a flag indicating if the frame protector will be used at client + * (is_client = true) or server (is_client = false) side. + * - is_rekey: a flag indicating if the frame protector will use an AEAD with + * rekeying. + * - max_protected_frame_size: an in/out parameter indicating max frame size + * to be used by the frame protector. If it is nullptr, the default frame + * size will be used. Otherwise, the provided frame size will be adjusted (if + * not falling into a valid frame range) and used. + * - self: a pointer to the frame protector returned from the method. + * + * This method returns TSI_OK on success and TSI_INTERNAL_ERROR otherwise. + */ +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); + +#endif /* GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_FRAME_PROTECTOR_H */ diff --git a/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.cc b/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.cc new file mode 100644 index 0000000000..0574ed5012 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.cc @@ -0,0 +1,114 @@ +/* + * + * 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_record_protocol_crypter_common.h" + +#include <grpc/support/alloc.h> + +static void maybe_copy_error_msg(const char* src, char** dst) { + if (dst != nullptr && src != nullptr) { + *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1)); + memcpy(*dst, src, strlen(src) + 1); + } +} + +grpc_status_code input_sanity_check( + const alts_record_protocol_crypter* rp_crypter, const unsigned char* data, + size_t* output_size, char** error_details) { + if (rp_crypter == nullptr) { + maybe_copy_error_msg("alts_crypter instance is nullptr.", error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } else if (data == nullptr) { + maybe_copy_error_msg("data is nullptr.", error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } else if (output_size == nullptr) { + maybe_copy_error_msg("output_size is nullptr.", error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + return GRPC_STATUS_OK; +} + +grpc_status_code increment_counter(alts_record_protocol_crypter* rp_crypter, + char** error_details) { + bool is_overflow = false; + grpc_status_code status = + alts_counter_increment(rp_crypter->ctr, &is_overflow, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + if (is_overflow) { + const char error_msg[] = + "crypter counter is wrapped. The connection" + "should be closed and the key should be deleted."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INTERNAL; + } + return GRPC_STATUS_OK; +} + +size_t alts_record_protocol_crypter_num_overhead_bytes(const alts_crypter* c) { + if (c != nullptr) { + size_t num_overhead_bytes = 0; + char* error_details = nullptr; + const alts_record_protocol_crypter* rp_crypter = + reinterpret_cast<const alts_record_protocol_crypter*>(c); + grpc_status_code status = gsec_aead_crypter_tag_length( + rp_crypter->crypter, &num_overhead_bytes, &error_details); + if (status == GRPC_STATUS_OK) { + return num_overhead_bytes; + } + } + return 0; +} + +void alts_record_protocol_crypter_destruct(alts_crypter* c) { + if (c != nullptr) { + alts_record_protocol_crypter* rp_crypter = + reinterpret_cast<alts_record_protocol_crypter*>(c); + alts_counter_destroy(rp_crypter->ctr); + gsec_aead_crypter_destroy(rp_crypter->crypter); + } +} + +alts_record_protocol_crypter* alts_crypter_create_common( + gsec_aead_crypter* crypter, bool is_client, size_t overflow_size, + char** error_details) { + if (crypter != nullptr) { + auto* rp_crypter = static_cast<alts_record_protocol_crypter*>( + gpr_malloc(sizeof(alts_record_protocol_crypter))); + size_t counter_size = 0; + grpc_status_code status = + gsec_aead_crypter_nonce_length(crypter, &counter_size, error_details); + if (status != GRPC_STATUS_OK) { + return nullptr; + } + /* Create a counter. */ + status = alts_counter_create(is_client, counter_size, overflow_size, + &rp_crypter->ctr, error_details); + if (status != GRPC_STATUS_OK) { + return nullptr; + } + rp_crypter->crypter = crypter; + return rp_crypter; + } + const char error_msg[] = "crypter is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return nullptr; +} diff --git a/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h b/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h new file mode 100644 index 0000000000..682a8f7e7a --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h @@ -0,0 +1,114 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_RECORD_PROTOCOL_CRYPTER_COMMON_H +#define GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_RECORD_PROTOCOL_CRYPTER_COMMON_H + +#include <grpc/support/port_platform.h> + +#include <grpc/grpc.h> + +#include "src/core/tsi/alts/frame_protector/alts_counter.h" +#include "src/core/tsi/alts/frame_protector/alts_crypter.h" + +/** + * This file contains common implementation that will be used in both seal and + * unseal operations. + */ + +/** + * Main struct for alts_record_protocol_crypter that will be used in both + * seal and unseal operations. + */ +typedef struct alts_record_protocol_crypter { + alts_crypter base; + gsec_aead_crypter* crypter; + alts_counter* ctr; +} alts_record_protocol_crypter; + +/** + * This method performs input sanity checks on a subset of inputs to + * alts_crypter_process_in_place() for both seal and unseal operations. + * + * - rp_crypter: an alts_record_protocol_crypter instance. + * - data: it represents raw data that needs to be sealed in a seal operation or + * protected data that needs to be unsealed in an unseal operation. + * - output_size: size of data written to the data buffer after a seal or + * unseal operation. + * - error_details: a buffer containing an error message if any of checked + * inputs is nullptr. It is legal to pass nullptr into error_details and + * otherwise, the parameter should be freed with gpr_free. + * + * On success, the method returns GRPC_STATUS_OK. Otherwise, + * it returns an error status code along with its details specified in + * error_details (if error_details is not nullptr). + */ +grpc_status_code input_sanity_check( + const alts_record_protocol_crypter* rp_crypter, const unsigned char* data, + size_t* output_size, char** error_details); + +/** + * This method increments the counter within an alts_record_protocol_crypter + * instance. + * + * - rp_crypter: an alts_record_protocol_crypter instance. + * - error_details: a buffer containing an error message if the method does not + * function correctly or the counter is wrapped. It is legal to pass nullptr + * into error_details and otherwise, the parameter should be freed with + * gpr_free. + * + * On success, the method returns GRPC_STATUS_OK. Otherwise, + * it returns an error status code along with its details specified in + * error_details (if error_details is not nullptr). + */ +grpc_status_code increment_counter(alts_record_protocol_crypter* rp_crypter, + char** error_details); + +/** + * This method creates an alts_crypter instance, and populates the fields + * that are common to both seal and unseal operations. + * + * - crypter: a gsec_aead_crypter instance used to perform AEAD decryption. The + * function does not take ownership of crypter. + * - is_client: a flag indicating if the alts_crypter instance will be + * used at the client (is_client = true) or server (is_client = + * false) side. + * - overflow_size: overflow size of counter in bytes. + * - error_details: a buffer containing an error message if the method does + * not function correctly. It is legal to pass nullptr into error_details, and + * otherwise, the parameter should be freed with gpr_free. + * + * On success of creation, the method returns alts_record_protocol_crypter + * instance. Otherwise, it returns nullptr with its details specified in + * error_details (if error_details is not nullptr). + * + */ +alts_record_protocol_crypter* alts_crypter_create_common( + gsec_aead_crypter* crypter, bool is_client, size_t overflow_size, + char** error_details); + +/** + * For the following two methods, please refer to the corresponding API in + * alts_crypter.h for detailed specifications. + */ +size_t alts_record_protocol_crypter_num_overhead_bytes(const alts_crypter* c); + +void alts_record_protocol_crypter_destruct(alts_crypter* c); + +#endif /* GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_ALTS_RECORD_PROTOCOL_CRYPTER_COMMON_H \ + */ diff --git a/src/core/tsi/alts/frame_protector/alts_seal_privacy_integrity_crypter.cc b/src/core/tsi/alts/frame_protector/alts_seal_privacy_integrity_crypter.cc new file mode 100644 index 0000000000..f407831613 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_seal_privacy_integrity_crypter.cc @@ -0,0 +1,105 @@ +/* + * + * 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 <grpc/support/alloc.h> + +#include "src/core/tsi/alts/frame_protector/alts_counter.h" +#include "src/core/tsi/alts/frame_protector/alts_crypter.h" +#include "src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h" + +static void maybe_copy_error_msg(const char* src, char** dst) { + if (dst != nullptr && src != nullptr) { + *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1)); + memcpy(*dst, src, strlen(src) + 1); + } +} + +/* Perform input santity check for a seal operation. */ +static grpc_status_code seal_check(alts_crypter* c, const unsigned char* data, + size_t data_allocated_size, size_t data_size, + size_t* output_size, char** error_details) { + /* Do common input sanity check. */ + grpc_status_code status = input_sanity_check( + reinterpret_cast<const alts_record_protocol_crypter*>(c), data, + output_size, error_details); + if (status != GRPC_STATUS_OK) return status; + /* Do seal-specific check. */ + size_t num_overhead_bytes = + alts_crypter_num_overhead_bytes(reinterpret_cast<const alts_crypter*>(c)); + if (data_size == 0) { + const char error_msg[] = "data_size is zero."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + if (data_size + num_overhead_bytes > data_allocated_size) { + const char error_msg[] = + "data_allocated_size is smaller than sum of data_size and " + "num_overhead_bytes."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + return GRPC_STATUS_OK; +} + +static grpc_status_code alts_seal_crypter_process_in_place( + alts_crypter* c, unsigned char* data, size_t data_allocated_size, + size_t data_size, size_t* output_size, char** error_details) { + grpc_status_code status = seal_check(c, data, data_allocated_size, data_size, + output_size, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + /* Do AEAD encryption. */ + alts_record_protocol_crypter* rp_crypter = + reinterpret_cast<alts_record_protocol_crypter*>(c); + status = gsec_aead_crypter_encrypt( + rp_crypter->crypter, alts_counter_get_counter(rp_crypter->ctr), + alts_counter_get_size(rp_crypter->ctr), nullptr /* aad */, + 0 /* aad_length */, data, data_size, data, data_allocated_size, + output_size, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + /* Increment the crypter counter. */ + return increment_counter(rp_crypter, error_details); +} + +static const alts_crypter_vtable vtable = { + alts_record_protocol_crypter_num_overhead_bytes, + alts_seal_crypter_process_in_place, alts_record_protocol_crypter_destruct}; + +grpc_status_code alts_seal_crypter_create(gsec_aead_crypter* gc, bool is_client, + size_t overflow_size, + alts_crypter** crypter, + char** error_details) { + if (crypter == nullptr) { + const char error_msg[] = "crypter is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_FAILED_PRECONDITION; + } + alts_record_protocol_crypter* rp_crypter = + alts_crypter_create_common(gc, !is_client, overflow_size, error_details); + if (rp_crypter == nullptr) { + return GRPC_STATUS_FAILED_PRECONDITION; + } + rp_crypter->base.vtable = &vtable; + *crypter = &rp_crypter->base; + return GRPC_STATUS_OK; +} diff --git a/src/core/tsi/alts/frame_protector/alts_unseal_privacy_integrity_crypter.cc b/src/core/tsi/alts/frame_protector/alts_unseal_privacy_integrity_crypter.cc new file mode 100644 index 0000000000..51bea24f1f --- /dev/null +++ b/src/core/tsi/alts/frame_protector/alts_unseal_privacy_integrity_crypter.cc @@ -0,0 +1,103 @@ +/* + * + * 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 <grpc/support/alloc.h> + +#include "src/core/tsi/alts/frame_protector/alts_counter.h" +#include "src/core/tsi/alts/frame_protector/alts_crypter.h" +#include "src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h" + +static void maybe_copy_error_msg(const char* src, char** dst) { + if (dst != nullptr && src != nullptr) { + *dst = static_cast<char*>(gpr_malloc(strlen(src) + 1)); + memcpy(*dst, src, strlen(src) + 1); + } +} + +/* Perform input santity check. */ +static grpc_status_code unseal_check(alts_crypter* c, const unsigned char* data, + size_t data_allocated_size, + size_t data_size, size_t* output_size, + char** error_details) { + /* Do common input sanity check. */ + grpc_status_code status = input_sanity_check( + reinterpret_cast<const alts_record_protocol_crypter*>(c), data, + output_size, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + /* Do unseal-specific input check. */ + size_t num_overhead_bytes = + alts_crypter_num_overhead_bytes(reinterpret_cast<const alts_crypter*>(c)); + if (num_overhead_bytes > data_size) { + const char error_msg[] = "data_size is smaller than num_overhead_bytes."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_INVALID_ARGUMENT; + } + return GRPC_STATUS_OK; +} + +static grpc_status_code alts_unseal_crypter_process_in_place( + alts_crypter* c, unsigned char* data, size_t data_allocated_size, + size_t data_size, size_t* output_size, char** error_details) { + grpc_status_code status = unseal_check(c, data, data_allocated_size, + data_size, output_size, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + /* Do AEAD decryption. */ + alts_record_protocol_crypter* rp_crypter = + reinterpret_cast<alts_record_protocol_crypter*>(c); + status = gsec_aead_crypter_decrypt( + rp_crypter->crypter, alts_counter_get_counter(rp_crypter->ctr), + alts_counter_get_size(rp_crypter->ctr), nullptr /* aad */, + 0 /* aad_length */, data, data_size, data, data_allocated_size, + output_size, error_details); + if (status != GRPC_STATUS_OK) { + return status; + } + /* Increment the crypter counter. */ + return increment_counter(rp_crypter, error_details); +} + +static const alts_crypter_vtable vtable = { + alts_record_protocol_crypter_num_overhead_bytes, + alts_unseal_crypter_process_in_place, + alts_record_protocol_crypter_destruct}; + +grpc_status_code alts_unseal_crypter_create(gsec_aead_crypter* gc, + bool is_client, + size_t overflow_size, + alts_crypter** crypter, + char** error_details) { + if (crypter == nullptr) { + const char error_msg[] = "crypter is nullptr."; + maybe_copy_error_msg(error_msg, error_details); + return GRPC_STATUS_FAILED_PRECONDITION; + } + alts_record_protocol_crypter* rp_crypter = + alts_crypter_create_common(gc, is_client, overflow_size, error_details); + if (rp_crypter == nullptr) { + return GRPC_STATUS_FAILED_PRECONDITION; + } + rp_crypter->base.vtable = &vtable; + *crypter = &rp_crypter->base; + return GRPC_STATUS_OK; +} diff --git a/src/core/tsi/alts/frame_protector/frame_handler.cc b/src/core/tsi/alts/frame_protector/frame_handler.cc new file mode 100644 index 0000000000..d3fda63b3d --- /dev/null +++ b/src/core/tsi/alts/frame_protector/frame_handler.cc @@ -0,0 +1,218 @@ +/* + * + * 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/frame_handler.h" + +#include <limits.h> +#include <stdint.h> +#include <string.h> + +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> + +#include "src/core/lib/gpr/useful.h" + +/* Use little endian to interpret a string of bytes as uint32_t. */ +static uint32_t load_32_le(const unsigned char* buffer) { + return (((uint32_t)buffer[3]) << 24) | (((uint32_t)buffer[2]) << 16) | + (((uint32_t)buffer[1]) << 8) | ((uint32_t)buffer[0]); +} + +/* Store uint32_t as a string of little endian bytes. */ +static void store_32_le(uint32_t value, unsigned char* buffer) { + buffer[3] = (unsigned char)(value >> 24) & 0xFF; + buffer[2] = (unsigned char)(value >> 16) & 0xFF; + buffer[1] = (unsigned char)(value >> 8) & 0xFF; + buffer[0] = (unsigned char)(value)&0xFF; +} + +/* Frame writer implementation. */ +alts_frame_writer* alts_create_frame_writer() { + alts_frame_writer* writer = + static_cast<alts_frame_writer*>(gpr_zalloc(sizeof(*writer))); + return writer; +} + +bool alts_reset_frame_writer(alts_frame_writer* writer, + const unsigned char* buffer, size_t length) { + if (buffer == nullptr) return false; + size_t max_input_size = SIZE_MAX - kFrameLengthFieldSize; + if (length > max_input_size) { + gpr_log(GPR_ERROR, "length must be at most %zu", max_input_size); + return false; + } + writer->input_buffer = buffer; + writer->input_size = length; + writer->input_bytes_written = 0; + writer->header_bytes_written = 0; + store_32_le( + static_cast<uint32_t>(writer->input_size + kFrameMessageTypeFieldSize), + writer->header_buffer); + store_32_le(kFrameMessageType, writer->header_buffer + kFrameLengthFieldSize); + return true; +} + +bool alts_write_frame_bytes(alts_frame_writer* writer, unsigned char* output, + size_t* bytes_size) { + if (bytes_size == nullptr || output == nullptr) return false; + if (alts_is_frame_writer_done(writer)) { + *bytes_size = 0; + return true; + } + size_t bytes_written = 0; + /* Write some header bytes, if needed. */ + if (writer->header_bytes_written != sizeof(writer->header_buffer)) { + size_t bytes_to_write = + GPR_MIN(*bytes_size, + sizeof(writer->header_buffer) - writer->header_bytes_written); + memcpy(output, writer->header_buffer + writer->header_bytes_written, + bytes_to_write); + bytes_written += bytes_to_write; + *bytes_size -= bytes_to_write; + writer->header_bytes_written += bytes_to_write; + output += bytes_to_write; + if (writer->header_bytes_written != sizeof(writer->header_buffer)) { + *bytes_size = bytes_written; + return true; + } + } + /* Write some non-header bytes. */ + size_t bytes_to_write = + GPR_MIN(writer->input_size - writer->input_bytes_written, *bytes_size); + memcpy(output, writer->input_buffer, bytes_to_write); + writer->input_buffer += bytes_to_write; + bytes_written += bytes_to_write; + writer->input_bytes_written += bytes_to_write; + *bytes_size = bytes_written; + return true; +} + +bool alts_is_frame_writer_done(alts_frame_writer* writer) { + return writer->input_buffer == nullptr || + writer->input_size == writer->input_bytes_written; +} + +size_t alts_get_num_writer_bytes_remaining(alts_frame_writer* writer) { + return (sizeof(writer->header_buffer) - writer->header_bytes_written) + + (writer->input_size - writer->input_bytes_written); +} + +void alts_destroy_frame_writer(alts_frame_writer* writer) { gpr_free(writer); } + +/* Frame reader implementation. */ +alts_frame_reader* alts_create_frame_reader() { + alts_frame_reader* reader = + static_cast<alts_frame_reader*>(gpr_zalloc(sizeof(*reader))); + return reader; +} + +bool alts_is_frame_reader_done(alts_frame_reader* reader) { + return reader->output_buffer == nullptr || + (reader->header_bytes_read == sizeof(reader->header_buffer) && + reader->bytes_remaining == 0); +} + +bool alts_has_read_frame_length(alts_frame_reader* reader) { + return sizeof(reader->header_buffer) == reader->header_bytes_read; +} + +size_t alts_get_reader_bytes_remaining(alts_frame_reader* reader) { + return alts_has_read_frame_length(reader) ? reader->bytes_remaining : 0; +} + +void alts_reset_reader_output_buffer(alts_frame_reader* reader, + unsigned char* buffer) { + reader->output_buffer = buffer; +} + +bool alts_reset_frame_reader(alts_frame_reader* reader, unsigned char* buffer) { + if (buffer == nullptr) return false; + reader->output_buffer = buffer; + reader->bytes_remaining = 0; + reader->header_bytes_read = 0; + reader->output_bytes_read = 0; + return true; +} + +bool alts_read_frame_bytes(alts_frame_reader* reader, + const unsigned char* bytes, size_t* bytes_size) { + if (bytes_size == nullptr) return false; + if (bytes == nullptr) { + *bytes_size = 0; + return false; + } + if (alts_is_frame_reader_done(reader)) { + *bytes_size = 0; + return true; + } + size_t bytes_processed = 0; + /* Process the header, if needed. */ + if (reader->header_bytes_read != sizeof(reader->header_buffer)) { + size_t bytes_to_write = GPR_MIN( + *bytes_size, sizeof(reader->header_buffer) - reader->header_bytes_read); + memcpy(reader->header_buffer + reader->header_bytes_read, bytes, + bytes_to_write); + reader->header_bytes_read += bytes_to_write; + bytes_processed += bytes_to_write; + bytes += bytes_to_write; + *bytes_size -= bytes_to_write; + if (reader->header_bytes_read != sizeof(reader->header_buffer)) { + *bytes_size = bytes_processed; + return true; + } + size_t frame_length = load_32_le(reader->header_buffer); + if (frame_length < kFrameMessageTypeFieldSize || + frame_length > kFrameMaxSize) { + gpr_log(GPR_ERROR, + "Bad frame length (should be at least %zu, and at most %zu)", + kFrameMessageTypeFieldSize, kFrameMaxSize); + *bytes_size = 0; + return false; + } + size_t message_type = + load_32_le(reader->header_buffer + kFrameLengthFieldSize); + if (message_type != kFrameMessageType) { + gpr_log(GPR_ERROR, "Unsupported message type %zu (should be %zu)", + message_type, kFrameMessageType); + *bytes_size = 0; + return false; + } + reader->bytes_remaining = frame_length - kFrameMessageTypeFieldSize; + } + /* Process the non-header bytes. */ + size_t bytes_to_write = GPR_MIN(*bytes_size, reader->bytes_remaining); + memcpy(reader->output_buffer, bytes, bytes_to_write); + reader->output_buffer += bytes_to_write; + bytes_processed += bytes_to_write; + reader->bytes_remaining -= bytes_to_write; + reader->output_bytes_read += bytes_to_write; + *bytes_size = bytes_processed; + return true; +} + +size_t alts_get_output_bytes_read(alts_frame_reader* reader) { + return reader->output_bytes_read; +} + +unsigned char* alts_get_output_buffer(alts_frame_reader* reader) { + return reader->output_buffer; +} + +void alts_destroy_frame_reader(alts_frame_reader* reader) { gpr_free(reader); } diff --git a/src/core/tsi/alts/frame_protector/frame_handler.h b/src/core/tsi/alts/frame_protector/frame_handler.h new file mode 100644 index 0000000000..a703ff40d3 --- /dev/null +++ b/src/core/tsi/alts/frame_protector/frame_handler.h @@ -0,0 +1,236 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_FRAME_HANDLER_H +#define GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_FRAME_HANDLER_H + +#include <grpc/support/port_platform.h> + +#include <stdbool.h> +#include <stdlib.h> + +const size_t kFrameMessageType = 0x06; +const size_t kFrameLengthFieldSize = 4; +const size_t kFrameMessageTypeFieldSize = 4; +const size_t kFrameMaxSize = 1024 * 1024; +const size_t kFrameHeaderSize = + kFrameLengthFieldSize + kFrameMessageTypeFieldSize; + +/** + * Implementation of frame reader and frame writer. All APIs in the + * header are thread-compatible. + */ + +/** + * Main struct for a frame writer. It reads frames from an input buffer, and + * writes the contents as raw bytes. It does not own the input buffer. + */ +typedef struct alts_frame_writer { + const unsigned char* input_buffer; + unsigned char header_buffer[kFrameHeaderSize]; + size_t input_bytes_written; + size_t header_bytes_written; + size_t input_size; +} alts_frame_writer; + +/** + * Main struct for a frame reader. It reads raw bytes and puts the framed + * result into an output buffer. It does not own the output buffer. + */ +typedef struct alts_frame_reader { + unsigned char* output_buffer; + unsigned char header_buffer[kFrameHeaderSize]; + size_t header_bytes_read; + size_t output_bytes_read; + size_t bytes_remaining; +} alts_frame_reader; + +/** + * This method creates a frame writer instance and initializes its internal + * states. + */ +alts_frame_writer* alts_create_frame_writer(); + +/** + * This method resets internal states of a frame writer and prepares to write + * a single frame. It does not take ownership of payload_buffer. + * The payload_buffer must outlive the writer. + * + * - writer: a frame writer instance. + * - buffer: a buffer storing full payload data to be framed. + * - length: size of payload data. + * + * The method returns true on success and false otherwise. + */ +bool alts_reset_frame_writer(alts_frame_writer* writer, + const unsigned char* buffer, size_t length); + +/** + * This method writes up to bytes_size bytes of a frame to output. + * + * - writer: a frame writer instance. + * - output: an output buffer used to store the frame. + * - bytes_size: an in/out parameter that stores the size of output buffer + * before the call, and gets written the number of frame bytes written to the + * buffer. + * + * The method returns true on success and false otherwise. + */ +bool alts_write_frame_bytes(alts_frame_writer* writer, unsigned char* output, + size_t* bytes_size); + +/** + * This method checks if a reset can be called to write a new frame. It returns + * true if it's the first time to frame a payload, or the current frame has + * been finished processing. It returns false if it's not ready yet to start a + * new frame (e.g., more payload data needs to be accumulated to process the + * current frame). + * + * if (alts_is_frame_writer_done(writer)) { + * // a new frame can be written, call reset. + * alts_reset_frame_writer(writer, payload_buffer, payload_size); + * } else { + * // accumulate more payload data until a full frame can be written. + * } + * + * - writer: a frame writer instance. + */ +bool alts_is_frame_writer_done(alts_frame_writer* writer); + +/** + * This method returns the number of bytes left to write before a complete frame + * is formed. + * + * - writer: a frame writer instance. + */ +size_t alts_get_num_writer_bytes_remaining(alts_frame_writer* writer); + +/** + * This method destroys a frame writer instance. + * + * - writer: a frame writer instance. + */ +void alts_destroy_frame_writer(alts_frame_writer* writer); + +/** + * This method creates a frame reader instance and initializes its internal + * states. + */ +alts_frame_reader* alts_create_frame_reader(); + +/** + * This method resets internal states of a frame reader (including setting its + * output_buffer with buffer), and prepares to write processed bytes to + * an output_buffer. It does not take ownership of buffer. The buffer must + * outlive reader. + * + * - reader: a frame reader instance. + * - buffer: an output buffer used to store deframed results. + * + * The method returns true on success and false otherwise. + */ +bool alts_reset_frame_reader(alts_frame_reader* reader, unsigned char* buffer); + +/** + * This method processes up to the number of bytes given in bytes_size. It may + * choose not to process all the bytes, if, for instance, more bytes are + * given to the method than required to complete the current frame. + * + * - reader: a frame reader instance. + * - bytes: a buffer that stores data to be processed. + * - bytes_size: an in/out parameter that stores the size of bytes before the + * call and gets written the number of bytes processed. + * + * The method returns true on success and false otherwise. + */ +bool alts_read_frame_bytes(alts_frame_reader* reader, + const unsigned char* bytes, size_t* bytes_size); + +/** + * This method checks if a frame length has been read. + * + * - reader: a frame reader instance. + * + * The method returns true if a frame length has been read and false otherwise. + */ +bool alts_has_read_frame_length(alts_frame_reader* reader); + +/** + * This method returns the number of bytes the frame reader intends to write. + * It may only be called if alts_has_read_frame_length() returns true. + * + * - reader: a frame reader instance. + */ +size_t alts_get_reader_bytes_remaining(alts_frame_reader* reader); + +/** + * This method resets output_buffer but does not otherwise modify other internal + * states of a frame reader instance. After being set, the new output_buffer + * will hold the deframed payload held by the original output_buffer. It does + * not take ownership of buffer. The buffer must outlive the reader. + * To distinguish between two reset methods on a frame reader, + * + * if (alts_fh_is_frame_reader_done(reader)) { + * // if buffer contains a full payload to be deframed, call reset. + * alts_reset_frame_reader(reader, buffer); + * } + * + * // if remaining buffer space is not enough to hold a full payload + * if (buffer_space_remaining < alts_get_reader_bytes_remaining(reader)) { + * // allocate enough space for a new buffer, copy back data processed so far, + * // and call reset. + * alts_reset_reader_output_buffer(reader, new_buffer). + * } + * + * - reader: a frame reader instance. + * - buffer: a buffer used to set reader's output_buffer. + */ +void alts_reset_reader_output_buffer(alts_frame_reader* reader, + unsigned char* buffer); + +/** + * This method checks if reset can be called to start processing a new frame. + * If true and reset was previously called, a full frame has been processed and + * the content of the frame is available in output_buffer. + + * - reader: a frame reader instance. + */ +bool alts_is_frame_reader_done(alts_frame_reader* reader); + +/** + * This method returns output_bytes_read of a frame reader instance. + * + * - reader: a frame reader instance. + */ +size_t alts_get_output_bytes_read(alts_frame_reader* reader); + +/** + * This method returns output_buffer of a frame reader instance. + * + * - reader: a frame reader instance. + */ +unsigned char* alts_get_output_buffer(alts_frame_reader* reader); + +/** + * This method destroys a frame reader instance. + * + * - reader: a frame reader instance. + */ +void alts_destroy_frame_reader(alts_frame_reader* reader); + +#endif /* GRPC_CORE_TSI_ALTS_FRAME_PROTECTOR_FRAME_HANDLER_H */ |