aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/tsi/alts/frame_protector
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/tsi/alts/frame_protector')
-rw-r--r--src/core/tsi/alts/frame_protector/alts_counter.cc118
-rw-r--r--src/core/tsi/alts/frame_protector/alts_counter.h98
-rw-r--r--src/core/tsi/alts/frame_protector/alts_crypter.cc66
-rw-r--r--src/core/tsi/alts/frame_protector/alts_crypter.h255
-rw-r--r--src/core/tsi/alts/frame_protector/alts_frame_protector.cc407
-rw-r--r--src/core/tsi/alts/frame_protector/alts_frame_protector.h55
-rw-r--r--src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.cc114
-rw-r--r--src/core/tsi/alts/frame_protector/alts_record_protocol_crypter_common.h114
-rw-r--r--src/core/tsi/alts/frame_protector/alts_seal_privacy_integrity_crypter.cc105
-rw-r--r--src/core/tsi/alts/frame_protector/alts_unseal_privacy_integrity_crypter.cc103
-rw-r--r--src/core/tsi/alts/frame_protector/frame_handler.cc218
-rw-r--r--src/core/tsi/alts/frame_protector/frame_handler.h236
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 */