blob: 65f5453d468f207f2df4e19520a9188d660c7c26 [file] [log] [blame]
/*
*
* Copyright 2015 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_TRANSPORT_SECURITY_INTERFACE_H
#define GRPC_CORE_TSI_TRANSPORT_SECURITY_INTERFACE_H
#include <grpc/support/port_platform.h>
#include <stdint.h>
#include <stdlib.h>
#include "src/core/lib/debug/trace.h"
/* --- tsi result --- */
typedef enum {
TSI_OK = 0,
TSI_UNKNOWN_ERROR = 1,
TSI_INVALID_ARGUMENT = 2,
TSI_PERMISSION_DENIED = 3,
TSI_INCOMPLETE_DATA = 4,
TSI_FAILED_PRECONDITION = 5,
TSI_UNIMPLEMENTED = 6,
TSI_INTERNAL_ERROR = 7,
TSI_DATA_CORRUPTED = 8,
TSI_NOT_FOUND = 9,
TSI_PROTOCOL_FAILURE = 10,
TSI_HANDSHAKE_IN_PROGRESS = 11,
TSI_OUT_OF_RESOURCES = 12,
TSI_ASYNC = 13,
TSI_HANDSHAKE_SHUTDOWN = 14,
TSI_CLOSE_NOTIFY = 15, // Indicates that the connection should be closed.
TSI_DRAIN_BUFFER = 16, // Indicates that the buffer used to store handshake
// data should be drained.
} tsi_result;
typedef enum {
TSI_SECURITY_MIN,
TSI_SECURITY_NONE = TSI_SECURITY_MIN,
TSI_INTEGRITY_ONLY,
TSI_PRIVACY_AND_INTEGRITY,
TSI_SECURITY_MAX = TSI_PRIVACY_AND_INTEGRITY,
} tsi_security_level;
typedef enum {
// Default option
TSI_DONT_REQUEST_CLIENT_CERTIFICATE,
TSI_REQUEST_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
TSI_REQUEST_CLIENT_CERTIFICATE_AND_VERIFY,
TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_BUT_DONT_VERIFY,
TSI_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY,
} tsi_client_certificate_request_type;
typedef enum {
// TSI implementation provides a normal frame protector. The caller
// should invoke tsi_handshaker_result_create_frame_protector() to
// generate the frame protector.
TSI_FRAME_PROTECTOR_NORMAL,
// TSI implementation provides a zero-copy frame protector. The caller
// should invoke tsi_handshaker_result_create_zero_copy_grpc_protector()
// to generate the frame protector.
TSI_FRAME_PROTECTOR_ZERO_COPY,
// TSI implementation provides both normal and zero-copy frame protectors.
// The caller should invoke either
// tsi_handshaker_result_create_frame_protector() or
// tsi_handshaker_result_create_zero_copy_grpc_protector() to generate
// the frame protector.
TSI_FRAME_PROTECTOR_NORMAL_OR_ZERO_COPY,
// TSI implementation does not provide any frame protector. This means
// that it is safe for the caller to send bytes unprotected on the wire.
TSI_FRAME_PROTECTOR_NONE,
} tsi_frame_protector_type;
typedef enum {
TSI_TLS1_2,
TSI_TLS1_3,
} tsi_tls_version;
const char* tsi_result_to_string(tsi_result result);
const char* tsi_security_level_to_string(tsi_security_level security_level);
/* --- tsi tracing --- */
extern grpc_core::TraceFlag tsi_tracing_enabled;
/* -- tsi_zero_copy_grpc_protector object --
This object protects and unprotects grpc slice buffers with zero or minimized
memory copy once the handshake is done. Implementations of this object must be
thread compatible. This object depends on grpc and the details of this object
is defined in transport_security_grpc.h. */
typedef struct tsi_zero_copy_grpc_protector tsi_zero_copy_grpc_protector;
/* --- tsi_frame_protector object ---
This object protects and unprotects buffers once the handshake is done.
Implementations of this object must be thread compatible. */
typedef struct tsi_frame_protector tsi_frame_protector;
/* Outputs protected frames.
- unprotected_bytes is an input only parameter and points to the data
to be protected.
- unprotected_bytes_size is an input/output parameter used by the caller to
specify how many bytes are available in unprotected_bytes. The output
value is the number of bytes consumed during the call.
- protected_output_frames points to a buffer allocated by the caller that
will be written.
- protected_output_frames_size is an input/output parameter used by the
caller to specify how many bytes are available in protected_output_frames.
As an output, this value indicates the number of bytes written.
- This method returns TSI_OK in case of success or a specific error code in
case of failure. Note that even if all the input unprotected bytes are
consumed, they may not have been processed into the returned protected
output frames. The caller should call the protect_flush method
to make sure that there are no more protected bytes buffered in the
protector.
A typical way to call this method would be:
------------------------------------------------------------------------
unsigned char protected_buffer[4096];
size_t protected_buffer_size = sizeof(protected_buffer);
tsi_result result = TSI_OK;
while (message_size > 0) {
size_t protected_buffer_size_to_send = protected_buffer_size;
size_t processed_message_size = message_size;
result = tsi_frame_protector_protect(protector,
message_bytes,
&processed_message_size,
protected_buffer,
&protected_buffer_size_to_send);
if (result != TSI_OK) break;
send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send);
message_bytes += processed_message_size;
message_size -= processed_message_size;
// Don't forget to flush.
if (message_size == 0) {
size_t still_pending_size;
do {
protected_buffer_size_to_send = protected_buffer_size;
result = tsi_frame_protector_protect_flush(
protector, protected_buffer,
&protected_buffer_size_to_send, &still_pending_size);
if (result != TSI_OK) break;
send_bytes_to_peer(protected_buffer, protected_buffer_size_to_send);
} while (still_pending_size > 0);
}
}
if (result != TSI_OK) HandleError(result);
------------------------------------------------------------------------ */
tsi_result tsi_frame_protector_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);
/* Indicates that we need to flush the bytes buffered in the protector and get
the resulting frame.
- protected_output_frames points to a buffer allocated by the caller that
will be written.
- protected_output_frames_size is an input/output parameter used by the
caller to specify how many bytes are available in protected_output_frames.
- still_pending_bytes is an output parameter indicating the number of bytes
that still need to be flushed from the protector.*/
tsi_result tsi_frame_protector_protect_flush(
tsi_frame_protector* self, unsigned char* protected_output_frames,
size_t* protected_output_frames_size, size_t* still_pending_size);
/* Outputs unprotected bytes.
- protected_frames_bytes is an input only parameter and points to the
protected frames to be unprotected.
- protected_frames_bytes_size is an input/output only parameter used by the
caller to specify how many bytes are available in protected_bytes. The
output value is the number of bytes consumed during the call.
Implementations will buffer up to a frame of protected data.
- unprotected_bytes points to a buffer allocated by the caller that will be
written.
- unprotected_bytes_size is an input/output parameter used by the caller to
specify how many bytes are available in unprotected_bytes. This
value is expected to be at most max_protected_frame_size minus overhead
which means that max_protected_frame_size is a safe bet. The output value
is the number of bytes actually written.
If *unprotected_bytes_size is unchanged, there may be more data remaining
to unprotect, and the caller should call this function again.
- This method returns TSI_OK in case of success. Success includes cases where
there is not enough data to output a frame in which case
unprotected_bytes_size will be set to 0 and cases where the internal buffer
needs to be read before new protected data can be processed in which case
protected_frames_size will be set to 0. */
tsi_result tsi_frame_protector_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);
/* Destroys the tsi_frame_protector object. */
void tsi_frame_protector_destroy(tsi_frame_protector* self);
/* --- tsi_peer objects ---
tsi_peer objects are a set of properties. The peer owns the properties. */
/* This property is of type TSI_PEER_PROPERTY_STRING. */
#define TSI_CERTIFICATE_TYPE_PEER_PROPERTY "certificate_type"
/* This property represents security level of a channel. */
#define TSI_SECURITY_LEVEL_PEER_PROPERTY "security_level"
/* Property values may contain NULL characters just like C++ strings.
The length field gives the length of the string. */
typedef struct tsi_peer_property {
char* name;
struct {
char* data;
size_t length;
} value;
} tsi_peer_property;
struct tsi_peer {
tsi_peer_property* properties;
size_t property_count;
};
/* Destructs the tsi_peer object. */
void tsi_peer_destruct(tsi_peer* self);
/* --- tsi_handshaker_result object ---
This object contains all necessary handshake results and data such as peer
info, negotiated keys, unused handshake bytes, when the handshake completes.
Implementations of this object must be thread compatible. */
typedef struct tsi_handshaker_result tsi_handshaker_result;
/* This method extracts tsi peer. It returns TSI_OK assuming there is no fatal
error.
The caller is responsible for destructing the peer. */
tsi_result tsi_handshaker_result_extract_peer(const tsi_handshaker_result* self,
tsi_peer* peer);
/* This method indicates what type of frame protector is provided by the
TSI implementation. */
tsi_result tsi_handshaker_result_get_frame_protector_type(
const tsi_handshaker_result* self,
tsi_frame_protector_type* frame_protector_type);
/* This method creates a tsi_frame_protector object. It returns TSI_OK assuming
there is no fatal error.
The caller is responsible for destroying the protector. */
tsi_result tsi_handshaker_result_create_frame_protector(
const tsi_handshaker_result* self, size_t* max_output_protected_frame_size,
tsi_frame_protector** protector);
/* This method returns the unused bytes from the handshake. It returns TSI_OK
assuming there is no fatal error.
Ownership of the bytes is retained by the handshaker result. As a
consequence, the caller must not free the bytes. */
tsi_result tsi_handshaker_result_get_unused_bytes(
const tsi_handshaker_result* self, const unsigned char** bytes,
size_t* bytes_size);
/* This method releases the tsi_handshaker_handshaker object. After this method
is called, no other method can be called on the object. */
void tsi_handshaker_result_destroy(tsi_handshaker_result* self);
/* --- tsi_handshaker objects ----
Implementations of this object must be thread compatible.
------------------------------------------------------------------------
A typical usage supporting both synchronous and asynchronous TSI handshaker
implementations would be:
------------------------------------------------------------------------
typedef struct {
tsi_handshaker *handshaker;
tsi_handshaker_result *handshaker_result;
unsigned char *handshake_buffer;
size_t handshake_buffer_size;
...
} security_handshaker;
void do_handshake(security_handshaker *h, ...) {
// Start the handshake by the calling do_handshake_next.
do_handshake_next(h, NULL, 0);
...
}
// This method is the callback function when data is received from the
// peer. This method will read bytes into the handshake buffer and call
// do_handshake_next.
void on_handshake_data_received_from_peer(void *user_data) {
security_handshaker *h = (security_handshaker *)user_data;
size_t bytes_received_size = h->handshake_buffer_size;
read_bytes_from_peer(h->handshake_buffer, &bytes_received_size);
do_handshake_next(h, h->handshake_buffer, bytes_received_size);
}
// This method processes a step of handshake, calling tsi_handshaker_next.
void do_handshake_next(security_handshaker *h,
const unsigned char* bytes_received,
size_t bytes_received_size) {
tsi_result status = TSI_OK;
unsigned char *bytes_to_send = NULL;
size_t bytes_to_send_size = 0;
tsi_handshaker_result *result = NULL;
status = tsi_handshaker_next(
handshaker, bytes_received, bytes_received_size, &bytes_to_send,
&bytes_to_send_size, &result, on_handshake_next_done, h);
// If TSI handshaker is asynchronous, on_handshake_next_done will be
// executed inside tsi_handshaker_next.
if (status == TSI_ASYNC) return;
// If TSI handshaker is synchronous, invoke callback directly in this
// thread.
on_handshake_next_done(status, (void *)h, bytes_to_send,
bytes_to_send_size, result);
}
// This is the callback function to execute after tsi_handshaker_next.
// It is passed to tsi_handshaker_next as a function parameter.
void on_handshake_next_done(
tsi_result status, void *user_data, const unsigned char *bytes_to_send,
size_t bytes_to_send_size, tsi_handshaker_result *result) {
security_handshaker *h = (security_handshaker *)user_data;
if (status == TSI_INCOMPLETE_DATA) {
// Schedule an asynchronous read from the peer. If handshake data are
// received, on_handshake_data_received_from_peer will be called.
async_read_from_peer(..., ..., on_handshake_data_received_from_peer);
return;
}
if (status != TSI_OK) return;
if (bytes_to_send_size > 0) {
send_bytes_to_peer(bytes_to_send, bytes_to_send_size);
}
if (result != NULL) {
// Handshake completed.
h->result = result;
// Check the Peer.
tsi_peer peer;
status = tsi_handshaker_result_extract_peer(result, &peer);
if (status != TSI_OK) return;
status = check_peer(&peer);
tsi_peer_destruct(&peer);
if (status != TSI_OK) return;
// Create the protector.
tsi_frame_protector* protector = NULL;
status = tsi_handshaker_result_create_frame_protector(result, NULL,
&protector);
if (status != TSI_OK) return;
// Do not forget to unprotect outstanding data if any.
....
}
}
------------------------------------------------------------------------ */
typedef struct tsi_handshaker tsi_handshaker;
/* TODO(jiangtaoli2016): Cleans up deprecated methods when we are ready. */
/* TO BE DEPRECATED SOON. Use tsi_handshaker_next instead.
Gets bytes that need to be sent to the peer.
- bytes is the buffer that will be written with the data to be sent to the
peer.
- bytes_size is an input/output parameter specifying the capacity of the
bytes parameter as input and the number of bytes written as output.
Returns TSI_OK if all the data to send to the peer has been written or if
nothing has to be sent to the peer (in which base bytes_size outputs to 0),
otherwise returns TSI_INCOMPLETE_DATA which indicates that this method
needs to be called again to get all the bytes to send to the peer (there
was more data to write than the specified bytes_size). In case of a fatal
error in the handshake, another specific error code is returned. */
tsi_result tsi_handshaker_get_bytes_to_send_to_peer(tsi_handshaker* self,
unsigned char* bytes,
size_t* bytes_size);
/* TO BE DEPRECATED SOON. Use tsi_handshaker_next instead.
Processes bytes received from the peer.
- bytes is the buffer containing the data.
- bytes_size is an input/output parameter specifying the size of the data as
input and the number of bytes consumed as output.
Return TSI_OK if the handshake has all the data it needs to process,
otherwise return TSI_INCOMPLETE_DATA which indicates that this method
needs to be called again to complete the data needed for processing. In
case of a fatal error in the handshake, another specific error code is
returned. */
tsi_result tsi_handshaker_process_bytes_from_peer(tsi_handshaker* self,
const unsigned char* bytes,
size_t* bytes_size);
/* TO BE DEPRECATED SOON.
Gets the result of the handshaker.
Returns TSI_OK if the hanshake completed successfully and there has been no
errors. Returns TSI_HANDSHAKE_IN_PROGRESS if the handshaker is not done yet
but no error has been encountered so far. Otherwise the handshaker failed
with the returned error. */
tsi_result tsi_handshaker_get_result(tsi_handshaker* self);
/* TO BE DEPRECATED SOON.
Returns 1 if the handshake is in progress, 0 otherwise. */
#define tsi_handshaker_is_in_progress(h) \
(tsi_handshaker_get_result((h)) == TSI_HANDSHAKE_IN_PROGRESS)
/* TO BE DEPRECATED SOON. Use tsi_handshaker_result_extract_peer instead.
This method may return TSI_FAILED_PRECONDITION if
tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise
assuming the handshaker is not in a fatal error state.
The caller is responsible for destructing the peer. */
tsi_result tsi_handshaker_extract_peer(tsi_handshaker* self, tsi_peer* peer);
/* TO BE DEPRECATED SOON. Use tsi_handshaker_result_create_frame_protector
instead.
This method creates a tsi_frame_protector object after the handshake phase
is done. After this method has been called successfully, the only method
that can be called on this object is Destroy.
- max_output_protected_frame_size is an input/output parameter specifying the
desired max output protected frame size as input and outputing the actual
max output frame size as the output. Passing NULL is OK and will result in
the implementation choosing the default maximum protected frame size. Note
that this size only applies to outgoing frames (generated with
tsi_frame_protector_protect) and not incoming frames (input of
tsi_frame_protector_unprotect).
- protector is an output parameter pointing to the newly created
tsi_frame_protector object.
This method may return TSI_FAILED_PRECONDITION if
tsi_handshaker_is_in_progress returns 1, it returns TSI_OK otherwise assuming
the handshaker is not in a fatal error state.
The caller is responsible for destroying the protector. */
tsi_result tsi_handshaker_create_frame_protector(
tsi_handshaker* self, size_t* max_output_protected_frame_size,
tsi_frame_protector** protector);
/* Callback function definition for tsi_handshaker_next.
- status indicates the status of the next operation.
- user_data is the argument to callback function passed from the caller.
- bytes_to_send is the data buffer to be sent to the peer.
- bytes_to_send_size is the size of data buffer to be sent to the peer.
- handshaker_result is the result of handshake when the handshake completes,
is NULL otherwise. */
typedef void (*tsi_handshaker_on_next_done_cb)(
tsi_result status, void* user_data, const unsigned char* bytes_to_send,
size_t bytes_to_send_size, tsi_handshaker_result* handshaker_result);
/* Conduct a next step of the handshake.
- received_bytes is the buffer containing the data received from the peer.
- received_bytes_size is the size of the data received from the peer.
- bytes_to_send is the data buffer to be sent to the peer.
- bytes_to_send_size is the size of data buffer to be sent to the peer.
- handshaker_result is the result of handshake if the handshake completes.
- cb is the callback function defined above. It can be NULL for synchronous
TSI handshaker implementation.
- user_data is the argument to callback function passed from the caller.
This method returns TSI_ASYNC if the TSI handshaker implementation is
asynchronous, and in this case, the callback is guaranteed to run in another
thread owned by TSI. It returns TSI_OK if the handshake completes or if
there are data to send to the peer, otherwise returns TSI_INCOMPLETE_DATA
which indicates that this method needs to be called again with more data
from the peer. In case of a fatal error in the handshake, another specific
error code is returned.
The caller is responsible for destroying the handshaker_result. However,
the caller should not free bytes_to_send, as the buffer is owned by the
tsi_handshaker object. */
tsi_result tsi_handshaker_next(
tsi_handshaker* self, const unsigned char* received_bytes,
size_t received_bytes_size, const unsigned char** bytes_to_send,
size_t* bytes_to_send_size, tsi_handshaker_result** handshaker_result,
tsi_handshaker_on_next_done_cb cb, void* user_data);
/* This method shuts down a TSI handshake that is in progress.
*
* This method will be invoked when TSI handshake should be terminated before
* being finished in order to free any resources being used.
*/
void tsi_handshaker_shutdown(tsi_handshaker* self);
/* This method releases the tsi_handshaker object. After this method is called,
no other method can be called on the object. */
void tsi_handshaker_destroy(tsi_handshaker* self);
/* This method initializes the necessary shared objects used for tsi
implementation. */
void tsi_init();
/* This method destroys the shared objects created by tsi_init. */
void tsi_destroy();
#endif /* GRPC_CORE_TSI_TRANSPORT_SECURITY_INTERFACE_H */