Skip to content

Commit

Permalink
Add back custom extension support for libssl (#1071)
Browse files Browse the repository at this point in the history
* Revert "Remove custom extensions support."

This reverts commit 0a3e07a.

* Update custom extension tests to use new 0-RTT configuration from e580e9d
  • Loading branch information
andrewhop authored Jul 6, 2023
1 parent a33d16a commit f2e2b3c
Show file tree
Hide file tree
Showing 13 changed files with 801 additions and 9 deletions.
89 changes: 88 additions & 1 deletion include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,92 @@ OPENSSL_EXPORT int SSL_export_keying_material(
const uint8_t *context, size_t context_len, int use_context);


// Custom extensions.
//
// The custom extension functions allow TLS extensions to be added to
// ClientHello and ServerHello messages.

// SSL_custom_ext_add_cb is a callback function that is called when the
// ClientHello (for clients) or ServerHello (for servers) is constructed. In
// the case of a server, this callback will only be called for a given
// extension if the ClientHello contained that extension – it's not possible to
// inject extensions into a ServerHello that the client didn't request.
//
// When called, |extension_value| will contain the extension number that is
// being considered for addition (so that a single callback can handle multiple
// extensions). If the callback wishes to include the extension, it must set
// |*out| to point to |*out_len| bytes of extension contents and return one. In
// this case, the corresponding |SSL_custom_ext_free_cb| callback will later be
// called with the value of |*out| once that data has been copied.
//
// If the callback does not wish to add an extension it must return zero.
//
// Alternatively, the callback can abort the connection by setting
// |*out_alert_value| to a TLS alert number and returning -1.
typedef int (*SSL_custom_ext_add_cb)(SSL *ssl, unsigned extension_value,
const uint8_t **out, size_t *out_len,
int *out_alert_value, void *add_arg);

// SSL_custom_ext_free_cb is a callback function that is called by AWS-LC iff
// an |SSL_custom_ext_add_cb| callback previously returned one. In that case,
// this callback is called and passed the |out| pointer that was returned by
// the add callback. This is to free any dynamically allocated data created by
// the add callback.
typedef void (*SSL_custom_ext_free_cb)(SSL *ssl, unsigned extension_value,
const uint8_t *out, void *add_arg);

// SSL_custom_ext_parse_cb is a callback function that is called by AWS-LC to
// parse an extension from the peer: that is from the ServerHello for a client
// and from the ClientHello for a server.
//
// When called, |extension_value| will contain the extension number and the
// contents of the extension are |contents_len| bytes at |contents|.
//
// The callback must return one to continue the handshake. Otherwise, if it
// returns zero, a fatal alert with value |*out_alert_value| is sent and the
// handshake is aborted.
typedef int (*SSL_custom_ext_parse_cb)(SSL *ssl, unsigned extension_value,
const uint8_t *contents,
size_t contents_len,
int *out_alert_value, void *parse_arg);

// SSL_extension_supported returns one iff AWS-LC internally handles
// extensions of type |extension_value|. This can be used to avoid registering
// custom extension handlers for extensions that a future version of AWS-LC
// may handle internally.
OPENSSL_EXPORT int SSL_extension_supported(unsigned extension_value);

// SSL_CTX_add_client_custom_ext registers callback functions for handling
// custom TLS extensions for client connections.
//
// If |add_cb| is NULL then an empty extension will be added in each
// ClientHello. Otherwise, see the comment for |SSL_custom_ext_add_cb| about
// this callback.
//
// The |free_cb| may be NULL if |add_cb| doesn't dynamically allocate data that
// needs to be freed.
//
// It returns one on success or zero on error. It's always an error to register
// callbacks for the same extension twice, or to register callbacks for an
// extension that AWS-LC handles internally. See |SSL_extension_supported| to
// discover, at runtime, which extensions AWS-LC handles internally.
OPENSSL_EXPORT int SSL_CTX_add_client_custom_ext(
SSL_CTX *ctx, unsigned extension_value, SSL_custom_ext_add_cb add_cb,
SSL_custom_ext_free_cb free_cb, void *add_arg,
SSL_custom_ext_parse_cb parse_cb, void *parse_arg);

// SSL_CTX_add_server_custom_ext is the same as
// |SSL_CTX_add_client_custom_ext|, but for server connections.
//
// Unlike on the client side, if |add_cb| is NULL no extension will be added.
// The |add_cb|, if any, will only be called if the ClientHello contained a
// matching extension.
OPENSSL_EXPORT int SSL_CTX_add_server_custom_ext(
SSL_CTX *ctx, unsigned extension_value, SSL_custom_ext_add_cb add_cb,
SSL_custom_ext_free_cb free_cb, void *add_arg,
SSL_custom_ext_parse_cb parse_cb, void *parse_arg);


// Sessions.
//
// An |SSL_SESSION| represents an SSL session that may be resumed in an
Expand Down Expand Up @@ -3785,7 +3871,8 @@ enum ssl_early_data_reason_t BORINGSSL_ENUM_INT {
// The application settings did not match the session.
ssl_early_data_alps_mismatch = 14,
// The value of the largest entry.
ssl_early_data_reason_max_value = ssl_early_data_alps_mismatch,
ssl_early_data_unsupported_with_custom_extension = 15,
ssl_early_data_reason_max_value = ssl_early_data_unsupported_with_custom_extension,
};

// SSL_get_early_data_reason returns details why 0-RTT was accepted or rejected
Expand Down
1 change: 1 addition & 0 deletions ssl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_library(
ssl

bio_ssl.cc
custom_extensions.cc
d1_both.cc
d1_lib.cc
d1_pkt.cc
Expand Down
265 changes: 265 additions & 0 deletions ssl/custom_extensions.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/* Copyright (c) 2014, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */

#include <openssl/ssl.h>

#include <assert.h>
#include <string.h>

#include <openssl/bytestring.h>
#include <openssl/err.h>
#include <openssl/mem.h>
#include <openssl/stack.h>

#include "internal.h"


BSSL_NAMESPACE_BEGIN

void SSL_CUSTOM_EXTENSION_free(SSL_CUSTOM_EXTENSION *custom_extension) {
OPENSSL_free(custom_extension);
}

static const SSL_CUSTOM_EXTENSION *custom_ext_find(
STACK_OF(SSL_CUSTOM_EXTENSION) *stack,
unsigned *out_index, uint16_t value) {
for (size_t i = 0; i < sk_SSL_CUSTOM_EXTENSION_num(stack); i++) {
const SSL_CUSTOM_EXTENSION *ext = sk_SSL_CUSTOM_EXTENSION_value(stack, i);
if (ext->value == value) {
if (out_index != NULL) {
*out_index = i;
}
return ext;
}
}

return NULL;
}

// default_add_callback is used as the |add_callback| when the user doesn't
// provide one. For servers, it does nothing while, for clients, it causes an
// empty extension to be included.
static int default_add_callback(SSL *ssl, unsigned extension_value,
const uint8_t **out, size_t *out_len,
int *out_alert_value, void *add_arg) {
if (ssl->server) {
return 0;
}
*out_len = 0;
return 1;
}

static int custom_ext_add_hello(SSL_HANDSHAKE *hs, CBB *extensions) {
SSL *const ssl = hs->ssl;
STACK_OF(SSL_CUSTOM_EXTENSION) *stack = ssl->ctx->client_custom_extensions;
if (ssl->server) {
stack = ssl->ctx->server_custom_extensions;
}

if (stack == NULL) {
return 1;
}

for (size_t i = 0; i < sk_SSL_CUSTOM_EXTENSION_num(stack); i++) {
const SSL_CUSTOM_EXTENSION *ext = sk_SSL_CUSTOM_EXTENSION_value(stack, i);

if (ssl->server &&
!(hs->custom_extensions.received & (1u << i))) {
// Servers cannot echo extensions that the client didn't send.
continue;
}

const uint8_t *contents;
size_t contents_len;
int alert = SSL_AD_DECODE_ERROR;
CBB contents_cbb;

switch (ext->add_callback(ssl, ext->value, &contents, &contents_len, &alert,
ext->add_arg)) {
case 1:
if (!CBB_add_u16(extensions, ext->value) ||
!CBB_add_u16_length_prefixed(extensions, &contents_cbb) ||
!CBB_add_bytes(&contents_cbb, contents, contents_len) ||
!CBB_flush(extensions)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
ERR_add_error_dataf("extension %u", (unsigned) ext->value);
if (ext->free_callback && 0 < contents_len) {
ext->free_callback(ssl, ext->value, contents, ext->add_arg);
}
return 0;
}

if (ext->free_callback && 0 < contents_len) {
ext->free_callback(ssl, ext->value, contents, ext->add_arg);
}

if (!ssl->server) {
assert((hs->custom_extensions.sent & (1u << i)) == 0);
hs->custom_extensions.sent |= (1u << i);
}
break;

case 0:
break;

default:
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
ERR_add_error_dataf("extension %u", (unsigned) ext->value);
return 0;
}
}

return 1;
}

int custom_ext_add_clienthello(SSL_HANDSHAKE *hs, CBB *extensions) {
return custom_ext_add_hello(hs, extensions);
}

int custom_ext_parse_serverhello(SSL_HANDSHAKE *hs, int *out_alert,
uint16_t value, const CBS *extension) {
SSL *const ssl = hs->ssl;
unsigned index;
const SSL_CUSTOM_EXTENSION *ext =
custom_ext_find(ssl->ctx->client_custom_extensions, &index, value);

if (// Unknown extensions are not allowed in a ServerHello.
ext == NULL ||
// Also, if we didn't send the extension, that's also unacceptable.
!(hs->custom_extensions.sent & (1u << index))) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
ERR_add_error_dataf("extension %u", (unsigned)value);
*out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
return 0;
}

if (ext->parse_callback != NULL &&
!ext->parse_callback(ssl, value, CBS_data(extension), CBS_len(extension),
out_alert, ext->parse_arg)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
ERR_add_error_dataf("extension %u", (unsigned)ext->value);
return 0;
}

return 1;
}

int custom_ext_parse_clienthello(SSL_HANDSHAKE *hs, int *out_alert,
uint16_t value, const CBS *extension) {
SSL *const ssl = hs->ssl;
unsigned index;
const SSL_CUSTOM_EXTENSION *ext =
custom_ext_find(ssl->ctx->server_custom_extensions, &index, value);

if (ext == NULL) {
return 1;
}

assert((hs->custom_extensions.received & (1u << index)) == 0);
hs->custom_extensions.received |= (1u << index);

if (ext->parse_callback &&
!ext->parse_callback(ssl, value, CBS_data(extension), CBS_len(extension),
out_alert, ext->parse_arg)) {
OPENSSL_PUT_ERROR(SSL, SSL_R_CUSTOM_EXTENSION_ERROR);
ERR_add_error_dataf("extension %u", (unsigned)ext->value);
return 0;
}

return 1;
}

int custom_ext_add_serverhello(SSL_HANDSHAKE *hs, CBB *extensions) {
return custom_ext_add_hello(hs, extensions);
}

// MAX_NUM_CUSTOM_EXTENSIONS is the maximum number of custom extensions that
// can be set on an |SSL_CTX|. It's determined by the size of the bitset used
// to track when an extension has been sent.
#define MAX_NUM_CUSTOM_EXTENSIONS \
(sizeof(((SSL_HANDSHAKE *)NULL)->custom_extensions.sent) * 8)

static int custom_ext_append(STACK_OF(SSL_CUSTOM_EXTENSION) **stack,
unsigned extension_value,
SSL_custom_ext_add_cb add_cb,
SSL_custom_ext_free_cb free_cb, void *add_arg,
SSL_custom_ext_parse_cb parse_cb,
void *parse_arg) {
if (add_cb == NULL ||
0xffff < extension_value ||
SSL_extension_supported(extension_value) ||
// Specifying a free callback without an add callback is nonsensical
// and an error.
(*stack != NULL &&
(MAX_NUM_CUSTOM_EXTENSIONS <= sk_SSL_CUSTOM_EXTENSION_num(*stack) ||
custom_ext_find(*stack, NULL, extension_value) != NULL))) {
return 0;
}

SSL_CUSTOM_EXTENSION *ext =
(SSL_CUSTOM_EXTENSION *)OPENSSL_malloc(sizeof(SSL_CUSTOM_EXTENSION));
if (ext == NULL) {
return 0;
}
ext->add_callback = add_cb;
ext->add_arg = add_arg;
ext->free_callback = free_cb;
ext->parse_callback = parse_cb;
ext->parse_arg = parse_arg;
ext->value = extension_value;

if (*stack == NULL) {
*stack = sk_SSL_CUSTOM_EXTENSION_new_null();
if (*stack == NULL) {
SSL_CUSTOM_EXTENSION_free(ext);
return 0;
}
}

if (!sk_SSL_CUSTOM_EXTENSION_push(*stack, ext)) {
SSL_CUSTOM_EXTENSION_free(ext);
if (sk_SSL_CUSTOM_EXTENSION_num(*stack) == 0) {
sk_SSL_CUSTOM_EXTENSION_free(*stack);
*stack = NULL;
}
return 0;
}

return 1;
}

BSSL_NAMESPACE_END

using namespace bssl;

int SSL_CTX_add_client_custom_ext(SSL_CTX *ctx, unsigned extension_value,
SSL_custom_ext_add_cb add_cb,
SSL_custom_ext_free_cb free_cb, void *add_arg,
SSL_custom_ext_parse_cb parse_cb,
void *parse_arg) {
return custom_ext_append(&ctx->client_custom_extensions, extension_value,
add_cb ? add_cb : default_add_callback, free_cb,
add_arg, parse_cb, parse_arg);
}

int SSL_CTX_add_server_custom_ext(SSL_CTX *ctx, unsigned extension_value,
SSL_custom_ext_add_cb add_cb,
SSL_custom_ext_free_cb free_cb, void *add_arg,
SSL_custom_ext_parse_cb parse_cb,
void *parse_arg) {
return custom_ext_append(&ctx->server_custom_extensions, extension_value,
add_cb ? add_cb : default_add_callback, free_cb,
add_arg, parse_cb, parse_arg);
}
Loading

0 comments on commit f2e2b3c

Please sign in to comment.