Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back custom extension support for libssl #1071

Merged
merged 10 commits into from
Jul 6, 2023
89 changes: 88 additions & 1 deletion include/openssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,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 OpenSSL iff
andrewhop marked this conversation as resolved.
Show resolved Hide resolved
// 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 OpenSSL 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 OpenSSL internally handles
// extensions of type |extension_value|. This can be used to avoid registering
// custom extension handlers for extensions that a future version of OpenSSL
// 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 OpenSSL handles internally. See |SSL_extension_supported| to
// discover, at runtime, which extensions OpenSSL 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 @@ -3776,7 +3862,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