Skip to content

Commit

Permalink
Npn Extension Functions (#3521)
Browse files Browse the repository at this point in the history
* Added npn extension
  • Loading branch information
maddeleine authored Sep 30, 2022
1 parent f3b6560 commit 9b91aca
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 37 deletions.
203 changes: 203 additions & 0 deletions tests/unit/s2n_npn_extension_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 "s2n_test.h"

#include <stdint.h>
#include <string.h>

#include "tls/s2n_tls.h"
#include "tls/extensions/s2n_npn.h"
#include "tls/extensions/s2n_client_alpn.h"
#include "testlib/s2n_testlib.h"
#include "tests/s2n_test.h"
#include "stuffer/s2n_stuffer.h"
#include "utils/s2n_safety.h"

#define HTTP11 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31
#define SPDY1 0x73, 0x70, 0x64, 0x79, 0x2f, 0x31
#define SPDY2 0x73, 0x70, 0x64, 0x79, 0x2f, 0x32
#define SPDY3 0x73, 0x70, 0x64, 0x79, 0x2f, 0x33

int main(int argc, char **argv)
{
BEGIN_TEST();

const char *protocols[] = { "http/1.1", "spdy/1", "spdy/2" };
const uint8_t protocols_count = s2n_array_len(protocols);

/* Should-send tests on the client side */
{
/* No connection */
EXPECT_FALSE(s2n_client_npn_extension.should_send(NULL));

/* No config */
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
EXPECT_FALSE(s2n_client_npn_extension.should_send(client_conn));

/* No application protocols set */
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
EXPECT_FALSE(s2n_client_npn_extension.should_send(client_conn));

/* Application protocols set but NPN not supported. In this case the ALPN extension will be sent. */
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_FALSE(s2n_client_npn_extension.should_send(client_conn));
EXPECT_TRUE(s2n_client_alpn_extension.should_send(client_conn));

/* Both ALPN and NPN extensions will be sent */
client_conn->config->npn_supported = true;
EXPECT_TRUE(s2n_client_npn_extension.should_send(client_conn));
EXPECT_TRUE(s2n_client_alpn_extension.should_send(client_conn));
}

/* Should-send tests on the server side */
{
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
EXPECT_NOT_NULL(server_conn);
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));

/* NPN not supported */
EXPECT_FALSE(s2n_server_npn_extension.should_send(server_conn));

/* NPN supported */
server_conn->config->npn_supported = true;
EXPECT_TRUE(s2n_server_npn_extension.should_send(server_conn));

/* Server has already negotiated a protocol with the ALPN extension */
uint8_t first_protocol_len = strlen(protocols[0]);
EXPECT_MEMCPY_SUCCESS(server_conn->application_protocol, protocols, first_protocol_len + 1);
EXPECT_FALSE(s2n_server_npn_extension.should_send(server_conn));
}

/* s2n_server_npn_send */
{
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
EXPECT_NOT_NULL(server_conn);
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));
DEFER_CLEANUP(struct s2n_stuffer out = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&out, 0));

EXPECT_SUCCESS(s2n_server_npn_extension.send(server_conn, &out));

uint8_t protocol_len = 0;
uint8_t protocol[UINT8_MAX] = { 0 };
for (size_t i = 0; i < protocols_count; i++) {
EXPECT_SUCCESS(s2n_stuffer_read_uint8(&out, &protocol_len));
EXPECT_EQUAL(protocol_len, strlen(protocols[i]));

EXPECT_SUCCESS(s2n_stuffer_read_bytes(&out, protocol, protocol_len));
EXPECT_BYTEARRAY_EQUAL(protocol, protocols[i], protocol_len);
}

EXPECT_EQUAL(s2n_stuffer_data_available(&out), 0);
}

/* s2n_server_npn_recv */
{
/* Client has no application protocols configured. Not sure how this
* could happen, but added to be thorough. */
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
DEFER_CLEANUP(struct s2n_stuffer extension = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&extension, 0));

EXPECT_SUCCESS(s2n_server_npn_extension.recv(client_conn, &extension));
EXPECT_NULL(s2n_get_application_protocol(client_conn));
}

/* NPN recv extension can read NPN send extension */
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));

DEFER_CLEANUP(struct s2n_stuffer extension = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&extension, 0));

EXPECT_SUCCESS(s2n_server_npn_extension.send(client_conn, &extension));
EXPECT_SUCCESS(s2n_server_npn_extension.recv(client_conn, &extension));

/* Server sent the same list that the client configured so the first protocol in the list is chosen */
EXPECT_NOT_NULL(s2n_get_application_protocol(client_conn));
EXPECT_BYTEARRAY_EQUAL(s2n_get_application_protocol(client_conn), protocols[0], strlen(protocols[0]));
}

/* No match exists */
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));

DEFER_CLEANUP(struct s2n_stuffer extension = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&extension, 0));

uint8_t protocol[] = { SPDY3 };
EXPECT_SUCCESS(s2n_stuffer_write_uint8(&extension, sizeof(protocol)));
EXPECT_SUCCESS(s2n_stuffer_write_bytes(&extension, protocol, sizeof(protocol)));

EXPECT_SUCCESS(s2n_server_npn_extension.recv(client_conn, &extension));

EXPECT_NULL(s2n_get_application_protocol(client_conn));
}

/* Multiple matches exist and server's preferred choice is selected */
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);
EXPECT_SUCCESS(s2n_config_set_protocol_preferences(config, protocols, protocols_count));
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));

DEFER_CLEANUP(struct s2n_stuffer extension = { 0 }, s2n_stuffer_free);
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&extension, 0));

uint8_t wire_bytes[] = {
/* Size and bytes of first protocol */
0x06, SPDY1,
/* Size and bytes of second protocol */
0x08, HTTP11,
/* Size and bytes of second protocol */
0x06, SPDY2,
};

EXPECT_SUCCESS(s2n_stuffer_write_bytes(&extension, wire_bytes, sizeof(wire_bytes)));
EXPECT_SUCCESS(s2n_server_npn_extension.recv(client_conn, &extension));

EXPECT_NOT_NULL(s2n_get_application_protocol(client_conn));

/* Client's second protocol is selected because the server prefers it over client's first protocol */
EXPECT_BYTEARRAY_EQUAL(s2n_get_application_protocol(client_conn), protocols[1], strlen(protocols[1]));
}
}

END_TEST();
}
52 changes: 18 additions & 34 deletions tls/extensions/s2n_client_alpn.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

#include "utils/s2n_safety.h"

static bool s2n_client_alpn_should_send(struct s2n_connection *conn);
bool s2n_client_alpn_should_send(struct s2n_connection *conn);
static int s2n_client_alpn_send(struct s2n_connection *conn, struct s2n_stuffer *out);
static int s2n_client_alpn_recv(struct s2n_connection *conn, struct s2n_stuffer *extension);

Expand All @@ -38,7 +38,7 @@ const s2n_extension_type s2n_client_alpn_extension = {
.if_missing = s2n_extension_noop_if_missing,
};

static bool s2n_client_alpn_should_send(struct s2n_connection *conn)
bool s2n_client_alpn_should_send(struct s2n_connection *conn)
{
struct s2n_blob *client_app_protocols;

Expand All @@ -60,47 +60,31 @@ static int s2n_client_alpn_send(struct s2n_connection *conn, struct s2n_stuffer

static int s2n_client_alpn_recv(struct s2n_connection *conn, struct s2n_stuffer *extension)
{
uint16_t size_of_all;
struct s2n_stuffer server_protos = {0};
struct s2n_blob *supported_protocols = NULL;
POSIX_GUARD(s2n_connection_get_protocol_preferences(conn, &supported_protocols));
POSIX_ENSURE_REF(supported_protocols);

struct s2n_blob *server_app_protocols;
POSIX_GUARD(s2n_connection_get_protocol_preferences(conn, &server_app_protocols));

if (!server_app_protocols->size) {
if (supported_protocols->size == 0) {
/* No protocols configured, nothing to do */
return S2N_SUCCESS;
}

POSIX_GUARD(s2n_stuffer_read_uint16(extension, &size_of_all));
if (size_of_all > s2n_stuffer_data_available(extension) || size_of_all < 3) {
uint16_t wire_size = 0;
POSIX_GUARD(s2n_stuffer_read_uint16(extension, &wire_size));
if (wire_size > s2n_stuffer_data_available(extension) || wire_size < 3) {
/* Malformed length, ignore the extension */
return S2N_SUCCESS;
}

struct s2n_blob client_protocols = { 0 };
POSIX_GUARD(s2n_blob_init(&client_protocols, s2n_stuffer_raw_read(extension, wire_size), wire_size));

struct s2n_stuffer server_protocols = { 0 };
POSIX_GUARD(s2n_stuffer_init(&server_protocols, supported_protocols));
POSIX_GUARD(s2n_stuffer_skip_write(&server_protocols, supported_protocols->size));

POSIX_GUARD_RESULT(s2n_select_server_preference_protocol(conn, &server_protocols, &client_protocols));

struct s2n_blob client_app_protocols = { 0 };
client_app_protocols.size = size_of_all;
client_app_protocols.data = s2n_stuffer_raw_read(extension, size_of_all);
POSIX_ENSURE_REF(client_app_protocols.data);

/* Find a matching protocol */
POSIX_GUARD(s2n_stuffer_init(&server_protos, server_app_protocols));
POSIX_GUARD(s2n_stuffer_skip_write(&server_protos, server_app_protocols->size));

while (s2n_stuffer_data_available(&server_protos) > 0) {
struct s2n_blob server_protocol = { 0 };
POSIX_ENSURE(s2n_result_is_ok(s2n_protocol_preferences_read(&server_protos, &server_protocol)),
S2N_ERR_BAD_MESSAGE);

bool is_match = false;
POSIX_ENSURE(s2n_result_is_ok(s2n_protocol_preferences_contain(&client_app_protocols, &server_protocol, &is_match)),
S2N_ERR_BAD_MESSAGE);

if (is_match) {
POSIX_CHECKED_MEMCPY(conn->application_protocol, server_protocol.data, server_protocol.size);
conn->application_protocol[server_protocol.size] = '\0';
return S2N_SUCCESS;
}
}
return S2N_SUCCESS;
}

Expand Down
1 change: 1 addition & 0 deletions tls/extensions/s2n_client_alpn.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "stuffer/s2n_stuffer.h"

extern const s2n_extension_type s2n_client_alpn_extension;
bool s2n_client_alpn_should_send(struct s2n_connection *conn);

/* Old-style extension functions -- remove after extensions refactor is complete */

Expand Down
81 changes: 81 additions & 0 deletions tls/extensions/s2n_npn.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 "tls/extensions/s2n_npn.h"
#include "tls/extensions/s2n_client_alpn.h"
#include "tls/extensions/s2n_server_alpn.h"
#include "tls/s2n_tls.h"
#include "tls/s2n_tls_parameters.h"
#include "tls/s2n_protocol_preferences.h"

#include "utils/s2n_safety.h"

bool s2n_npn_should_send(struct s2n_connection *conn)
{
return s2n_client_alpn_should_send(conn) && conn->config->npn_supported;
}

const s2n_extension_type s2n_client_npn_extension = {
.iana_value = TLS_EXTENSION_NPN,
.is_response = false,
.send = s2n_extension_send_noop,
.recv = s2n_extension_recv_noop,
.should_send = s2n_npn_should_send,
.if_missing = s2n_extension_noop_if_missing,
};

bool s2n_server_npn_should_send(struct s2n_connection *conn)
{
/* Only use the NPN extension to negotiate a protocol if we don't have
* an option to use the ALPN extension.
*/
return s2n_npn_should_send(conn) && !s2n_server_alpn_should_send(conn);
}

int s2n_server_npn_send(struct s2n_connection *conn, struct s2n_stuffer *out)
{
struct s2n_blob *app_protocols = NULL;
POSIX_GUARD(s2n_connection_get_protocol_preferences(conn, &app_protocols));
POSIX_ENSURE_REF(app_protocols);

POSIX_GUARD(s2n_stuffer_write(out, app_protocols));

return S2N_SUCCESS;
}

int s2n_server_npn_recv(struct s2n_connection *conn, struct s2n_stuffer *extension)
{
struct s2n_blob *supported_protocols = NULL;
POSIX_GUARD(s2n_connection_get_protocol_preferences(conn, &supported_protocols));
POSIX_ENSURE_REF(supported_protocols);

if (supported_protocols->size == 0) {
/* No protocols configured */
return S2N_SUCCESS;
}

POSIX_GUARD_RESULT(s2n_select_server_preference_protocol(conn, extension, supported_protocols));

return S2N_SUCCESS;
}

const s2n_extension_type s2n_server_npn_extension = {
.iana_value = TLS_EXTENSION_NPN,
.is_response = true,
.send = s2n_server_npn_send,
.recv = s2n_server_npn_recv,
.should_send = s2n_server_npn_should_send,
.if_missing = s2n_extension_noop_if_missing,
};
21 changes: 21 additions & 0 deletions tls/extensions/s2n_npn.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

#pragma once

#include "tls/extensions/s2n_extension_type.h"

extern const s2n_extension_type s2n_client_npn_extension;
extern const s2n_extension_type s2n_server_npn_extension;
Loading

0 comments on commit 9b91aca

Please sign in to comment.