blob: 58aba9b747e0db5271e5fbdb383b24421500ab70 [file] [log] [blame]
/*
*
* 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/zero_copy_frame_protector/alts_zero_copy_grpc_protector.h"
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/gpr/useful.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/slice/slice_internal.h"
#include "src/core/tsi/alts/crypt/gsec.h"
#include "src/core/tsi/alts/zero_copy_frame_protector/alts_grpc_integrity_only_record_protocol.h"
#include "src/core/tsi/alts/zero_copy_frame_protector/alts_grpc_privacy_integrity_record_protocol.h"
#include "src/core/tsi/alts/zero_copy_frame_protector/alts_grpc_record_protocol.h"
#include "src/core/tsi/alts/zero_copy_frame_protector/alts_iovec_record_protocol.h"
#include "src/core/tsi/transport_security_grpc.h"
constexpr size_t kMinFrameLength = 1024;
constexpr size_t kDefaultFrameLength = 16 * 1024;
constexpr size_t kMaxFrameLength = 1024 * 1024;
/**
* Main struct for alts_zero_copy_grpc_protector.
* We choose to have two alts_grpc_record_protocol objects and two sets of slice
* buffers: one for protect and the other for unprotect, so that protect and
* unprotect can be executed in parallel. Implementations of this object must be
* thread compatible.
*/
typedef struct alts_zero_copy_grpc_protector {
tsi_zero_copy_grpc_protector base;
alts_grpc_record_protocol* record_protocol;
alts_grpc_record_protocol* unrecord_protocol;
size_t max_protected_frame_size;
size_t max_unprotected_data_size;
grpc_slice_buffer unprotected_staging_sb;
grpc_slice_buffer protected_sb;
grpc_slice_buffer protected_staging_sb;
uint32_t parsed_frame_size;
} alts_zero_copy_grpc_protector;
/**
* Given a slice buffer, parses the first 4 bytes little-endian unsigned frame
* size and returns the total frame size including the frame field. Caller
* needs to make sure the input slice buffer has at least 4 bytes. Returns true
* on success and false on failure.
*/
static bool read_frame_size(const grpc_slice_buffer* sb,
uint32_t* total_frame_size) {
if (sb == nullptr || sb->length < kZeroCopyFrameLengthFieldSize) {
return false;
}
uint8_t frame_size_buffer[kZeroCopyFrameLengthFieldSize];
uint8_t* buf = frame_size_buffer;
/* Copies the first 4 bytes to a temporary buffer. */
size_t remaining = kZeroCopyFrameLengthFieldSize;
for (size_t i = 0; i < sb->count; i++) {
size_t slice_length = GRPC_SLICE_LENGTH(sb->slices[i]);
if (remaining <= slice_length) {
memcpy(buf, GRPC_SLICE_START_PTR(sb->slices[i]), remaining);
remaining = 0;
break;
} else {
memcpy(buf, GRPC_SLICE_START_PTR(sb->slices[i]), slice_length);
buf += slice_length;
remaining -= slice_length;
}
}
GPR_ASSERT(remaining == 0);
/* Gets little-endian frame size. */
uint32_t frame_size = (((uint32_t)frame_size_buffer[3]) << 24) |
(((uint32_t)frame_size_buffer[2]) << 16) |
(((uint32_t)frame_size_buffer[1]) << 8) |
((uint32_t)frame_size_buffer[0]);
if (frame_size > kMaxFrameLength) {
gpr_log(GPR_ERROR, "Frame size is larger than maximum frame size");
return false;
}
/* Returns frame size including frame length field. */
*total_frame_size =
static_cast<uint32_t>(frame_size + kZeroCopyFrameLengthFieldSize);
return true;
}
/**
* Creates an alts_grpc_record_protocol object, given key, key size, and flags
* to indicate whether the record_protocol object uses the rekeying AEAD,
* whether the object is for client or server, whether the object is for
* integrity-only or privacy-integrity mode, and whether the object is is used
* for protect or unprotect.
*/
static tsi_result create_alts_grpc_record_protocol(
const uint8_t* key, size_t key_size, bool is_rekey, bool is_client,
bool is_integrity_only, bool is_protect, bool enable_extra_copy,
alts_grpc_record_protocol** record_protocol) {
if (key == nullptr || record_protocol == nullptr) {
return TSI_INVALID_ARGUMENT;
}
grpc_status_code status;
gsec_aead_crypter* crypter = nullptr;
char* error_details = nullptr;
status = gsec_aes_gcm_aead_crypter_create(key, key_size, kAesGcmNonceLength,
kAesGcmTagLength, is_rekey,
&crypter, &error_details);
if (status != GRPC_STATUS_OK) {
gpr_log(GPR_ERROR, "Failed to create AEAD crypter, %s", error_details);
gpr_free(error_details);
return TSI_INTERNAL_ERROR;
}
size_t overflow_limit = is_rekey ? kAltsRecordProtocolRekeyFrameLimit
: kAltsRecordProtocolFrameLimit;
/* Creates alts_grpc_record_protocol with AEAD crypter ownership transferred.
*/
tsi_result result = is_integrity_only
? alts_grpc_integrity_only_record_protocol_create(
crypter, overflow_limit, is_client, is_protect,
enable_extra_copy, record_protocol)
: alts_grpc_privacy_integrity_record_protocol_create(
crypter, overflow_limit, is_client, is_protect,
record_protocol);
if (result != TSI_OK) {
gsec_aead_crypter_destroy(crypter);
return result;
}
return TSI_OK;
}
/* --- tsi_zero_copy_grpc_protector methods implementation. --- */
static tsi_result alts_zero_copy_grpc_protector_protect(
tsi_zero_copy_grpc_protector* self, grpc_slice_buffer* unprotected_slices,
grpc_slice_buffer* protected_slices) {
if (self == nullptr || unprotected_slices == nullptr ||
protected_slices == nullptr) {
gpr_log(GPR_ERROR, "Invalid nullptr arguments to zero-copy grpc protect.");
return TSI_INVALID_ARGUMENT;
}
alts_zero_copy_grpc_protector* protector =
reinterpret_cast<alts_zero_copy_grpc_protector*>(self);
/* Calls alts_grpc_record_protocol protect repeatly. */
while (unprotected_slices->length > protector->max_unprotected_data_size) {
grpc_slice_buffer_move_first(unprotected_slices,
protector->max_unprotected_data_size,
&protector->unprotected_staging_sb);
tsi_result status = alts_grpc_record_protocol_protect(
protector->record_protocol, &protector->unprotected_staging_sb,
protected_slices);
if (status != TSI_OK) {
return status;
}
}
return alts_grpc_record_protocol_protect(
protector->record_protocol, unprotected_slices, protected_slices);
}
static tsi_result alts_zero_copy_grpc_protector_unprotect(
tsi_zero_copy_grpc_protector* self, grpc_slice_buffer* protected_slices,
grpc_slice_buffer* unprotected_slices) {
if (self == nullptr || unprotected_slices == nullptr ||
protected_slices == nullptr) {
gpr_log(GPR_ERROR,
"Invalid nullptr arguments to zero-copy grpc unprotect.");
return TSI_INVALID_ARGUMENT;
}
alts_zero_copy_grpc_protector* protector =
reinterpret_cast<alts_zero_copy_grpc_protector*>(self);
grpc_slice_buffer_move_into(protected_slices, &protector->protected_sb);
/* Keep unprotecting each frame if possible. */
while (protector->protected_sb.length >= kZeroCopyFrameLengthFieldSize) {
if (protector->parsed_frame_size == 0) {
/* We have not parsed frame size yet. Parses frame size. */
if (!read_frame_size(&protector->protected_sb,
&protector->parsed_frame_size)) {
grpc_slice_buffer_reset_and_unref_internal(&protector->protected_sb);
return TSI_DATA_CORRUPTED;
}
}
if (protector->protected_sb.length < protector->parsed_frame_size) break;
/* At this point, protected_sb contains at least one frame of data. */
tsi_result status;
if (protector->protected_sb.length == protector->parsed_frame_size) {
status = alts_grpc_record_protocol_unprotect(protector->unrecord_protocol,
&protector->protected_sb,
unprotected_slices);
} else {
grpc_slice_buffer_move_first(&protector->protected_sb,
protector->parsed_frame_size,
&protector->protected_staging_sb);
status = alts_grpc_record_protocol_unprotect(
protector->unrecord_protocol, &protector->protected_staging_sb,
unprotected_slices);
}
protector->parsed_frame_size = 0;
if (status != TSI_OK) {
grpc_slice_buffer_reset_and_unref_internal(&protector->protected_sb);
return status;
}
}
return TSI_OK;
}
static void alts_zero_copy_grpc_protector_destroy(
tsi_zero_copy_grpc_protector* self) {
if (self == nullptr) {
return;
}
alts_zero_copy_grpc_protector* protector =
reinterpret_cast<alts_zero_copy_grpc_protector*>(self);
alts_grpc_record_protocol_destroy(protector->record_protocol);
alts_grpc_record_protocol_destroy(protector->unrecord_protocol);
grpc_slice_buffer_destroy_internal(&protector->unprotected_staging_sb);
grpc_slice_buffer_destroy_internal(&protector->protected_sb);
grpc_slice_buffer_destroy_internal(&protector->protected_staging_sb);
gpr_free(protector);
}
static const tsi_zero_copy_grpc_protector_vtable
alts_zero_copy_grpc_protector_vtable = {
alts_zero_copy_grpc_protector_protect,
alts_zero_copy_grpc_protector_unprotect,
alts_zero_copy_grpc_protector_destroy};
tsi_result alts_zero_copy_grpc_protector_create(
const uint8_t* key, size_t key_size, bool is_rekey, bool is_client,
bool is_integrity_only, bool enable_extra_copy,
size_t* max_protected_frame_size,
tsi_zero_copy_grpc_protector** protector) {
if (grpc_core::ExecCtx::Get() == nullptr || key == nullptr ||
protector == nullptr) {
gpr_log(
GPR_ERROR,
"Invalid nullptr arguments to alts_zero_copy_grpc_protector create.");
return TSI_INVALID_ARGUMENT;
}
/* Creates alts_zero_copy_protector. */
alts_zero_copy_grpc_protector* impl =
static_cast<alts_zero_copy_grpc_protector*>(
gpr_zalloc(sizeof(alts_zero_copy_grpc_protector)));
/* Creates alts_grpc_record_protocol objects. */
tsi_result status = create_alts_grpc_record_protocol(
key, key_size, is_rekey, is_client, is_integrity_only,
/*is_protect=*/true, enable_extra_copy, &impl->record_protocol);
if (status == TSI_OK) {
status = create_alts_grpc_record_protocol(
key, key_size, is_rekey, is_client, is_integrity_only,
/*is_protect=*/false, enable_extra_copy, &impl->unrecord_protocol);
if (status == TSI_OK) {
/* Sets maximum frame size. */
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_data_size =
alts_grpc_record_protocol_max_unprotected_data_size(
impl->record_protocol, max_protected_frame_size_to_set);
GPR_ASSERT(impl->max_unprotected_data_size > 0);
/* Allocates internal slice buffers. */
grpc_slice_buffer_init(&impl->unprotected_staging_sb);
grpc_slice_buffer_init(&impl->protected_sb);
grpc_slice_buffer_init(&impl->protected_staging_sb);
impl->parsed_frame_size = 0;
impl->base.vtable = &alts_zero_copy_grpc_protector_vtable;
*protector = &impl->base;
return TSI_OK;
}
}
/* Cleanup if create failed. */
alts_grpc_record_protocol_destroy(impl->record_protocol);
alts_grpc_record_protocol_destroy(impl->unrecord_protocol);
gpr_free(impl);
return TSI_INTERNAL_ERROR;
}