Added ALPN support (client and server).

This commit is contained in:
Thomas Pornin 2016-12-28 14:11:51 +01:00
parent 8bb8d74d4a
commit 05ac106834
9 changed files with 1172 additions and 727 deletions

View file

@ -945,6 +945,27 @@ typedef struct {
const unsigned char *cert_cur;
size_t cert_len;
/*
* List of supported protocol names (ALPN extension). If unset,
* (number of names is 0), then:
* - the client sends no ALPN extension;
* - the server ignores any incoming ALPN extension.
*
* Otherwise:
* - the client sends an ALPN extension with all the names;
* - the server selects the first protocol in its list that
* the client also supports, or fails (fatal alert 120)
* if the client sends an ALPN extension and there is no
* match.
*
* The 'selected_protocol' field contains 1+n if the matching
* name has index n in the list (the value is 0 if no match was
* performed, e.g. the peer did not send an ALPN extension).
*/
const char **protocol_names;
uint16_t protocol_names_num;
uint16_t selected_protocol;
/*
* Pointers to implementations; left to NULL for unsupported
* functions. For the raw hash functions, implementations are
@ -1064,6 +1085,32 @@ br_ssl_engine_remove_flags(br_ssl_engine_context *cc, uint32_t flags)
*/
#define BR_OPT_TOLERATE_NO_CLIENT_AUTH ((uint32_t)1 << 2)
/**
* \brief Behavioural flag: fail on application protocol mismatch.
*
* The ALPN extension ([RFC 7301](https://tools.ietf.org/html/rfc7301))
* allows the client to send a list of application protocol names, and
* the server to select one. A mismatch is one of the following occurrences:
*
* - On the client: the client sends a list of names, the server
* responds with a protocol name which is _not_ part of the list of
* names sent by the client.
*
* - On the server: the client sends a list of names, and the server
* is also configured with a list of names, but there is no common
* protocol name between the two lists.
*
* Normal behaviour in case of mismatch is to report no matching name
* (`br_ssl_engine_get_selected_protocol()` returns `NULL`) and carry on.
* If the flag is set, then a mismatch implies a protocol failure (if
* the mismatch is detected by the server, it will send a fatal alert).
*
* Note: even with this flag, `br_ssl_engine_get_selected_protocol()`
* may still return `NULL` if the client or the server does not send an
* ALPN extension at all.
*/
#define BR_OPT_FAIL_ON_ALPN_MISMATCH ((uint32_t)1 << 3)
/**
* \brief Set the minimum and maximum supported protocol versions.
*
@ -1119,6 +1166,65 @@ br_ssl_engine_set_x509(br_ssl_engine_context *cc, const br_x509_class **x509ctx)
cc->x509ctx = x509ctx;
}
/**
* \brief Set the supported protocol names.
*
* Protocol names are part of the ALPN extension ([RFC
* 7301](https://tools.ietf.org/html/rfc7301)). Each protocol name is a
* character string, containing no more than 255 characters (256 with the
* terminating zero). When names are set, then:
*
* - The client will send an ALPN extension, containing the names. If
* the server responds with an ALPN extension, the client will verify
* that the response contains one of its name, and report that name
* through `br_ssl_engine_get_selected_protocol()`.
*
* - The server will parse incoming ALPN extension (from clients), and
* try to find a common protocol; if none is found, the connection
* is aborted with a fatal alert. On match, a response ALPN extension
* is sent, and name is reported through
* `br_ssl_engine_get_selected_protocol()`.
*
* The provided array is linked in, and must remain valid while the
* connection is live.
*
* Names MUST NOT be empty. Names MUST NOT be longer than 255 characters
* (excluding the terminating 0).
*
* \param ctx SSL engine context.
* \param names list of protocol names (zero-terminated).
* \param num number of protocol names (MUST be 1 or more).
*/
static inline void
br_ssl_engine_set_protocol_names(br_ssl_engine_context *ctx,
const char **names, size_t num)
{
ctx->protocol_names = names;
ctx->protocol_names_num = num;
}
/**
* \brief Get the selected protocol.
*
* If this context was initialised with a non-empty list of protocol
* names, and both client and server sent ALPN extensions during the
* handshake, and a common name was found, then that name is returned.
* Otherwise, `NULL` is returned.
*
* The returned pointer is one of the pointers provided to the context
* with `br_ssl_engine_set_protocol_names()`.
*
* \return the selected protocol, or `NULL`.
*/
static inline const char *
br_ssl_engine_get_selected_protocol(br_ssl_engine_context *ctx)
{
unsigned k;
k = ctx->selected_protocol;
return (k == 0 || k == 0xFFFF) ? NULL : ctx->protocol_names[k - 1];
}
/**
* \brief Set a hash function implementation (by ID).
*
@ -3727,5 +3833,6 @@ int br_sslio_close(br_sslio_context *cc);
#define BR_ALERT_USER_CANCELED 90
#define BR_ALERT_NO_RENEGOTIATION 100
#define BR_ALERT_UNSUPPORTED_EXTENSION 110
#define BR_ALERT_NO_APPLICATION_PROTOCOL 120
#endif

File diff suppressed because it is too large Load diff

View file

@ -429,6 +429,21 @@ addr-ctx: hash_id
: ext-point-format-length ( -- len )
supported-curves if 6 else 0 then ;
\ Length of ALPN extension.
cc: ext-ALPN-length ( -- len ) {
size_t u, len;
if (ENG->protocol_names_num == 0) {
T0_PUSH(0);
T0_RET();
}
len = 6;
for (u = 0; u < ENG->protocol_names_num; u ++) {
len += 1 + strlen(ENG->protocol_names[u]);
}
T0_PUSH(len);
}
\ Write handshake message: ClientHello
: write-ClientHello ( -- )
{ ; total-ext-length }
@ -438,6 +453,7 @@ addr-ctx: hash_id
ext-reneg-length ext-sni-length + ext-frag-length +
ext-signatures-length +
ext-supported-curves-length + ext-point-format-length +
ext-ALPN-length +
>total-ext-length
\ ClientHello type
@ -542,6 +558,21 @@ addr-ctx: hash_id
0x0002 write16 \ extension length
0x0100 write16 \ value: 1 format: uncompressed
then
ext-ALPN-length dup if
0x0010 write16 \ extension type (16)
4 - dup write16 \ extension length
2- write16 \ list length
addr-protocol_names_num get16 0
begin
dup2 > while
dup copy-protocol-name
dup write8 addr-pad swap write-blob
1+
repeat
2drop
else
drop
then
ext-padding-amount 0< ifnot
0x0015 write16 \ extension value (21)
ext-padding-amount
@ -595,6 +626,24 @@ addr-ctx: hash_id
then
then ;
\ Read the ALPN extension from the server. It must contain a single name,
\ and that name must match one of our names.
: read-ALPN-from-server ( lim -- lim )
\ Extension contents length.
read16 open-elt
\ Length of list of names.
read16 open-elt
\ There should be a single name.
read8 addr-pad swap dup { len } read-blob
close-elt
close-elt
len test-protocol-name dup 0< if
3 flag? if ERR_UNEXPECTED fail then
drop
else
1+ addr-selected_protocol set16
then ;
\ Save a value in a 16-bit field, or check it in case of session resumption.
: check-resume ( val addr resume -- )
if get16 = ifnot ERR_RESUME_MISMATCH fail then else set16 then ;
@ -688,6 +737,7 @@ cc: DEBUG-BLOB ( addr len -- ) {
ext-signatures-length { ok-signatures }
ext-supported-curves-length { ok-curves }
ext-point-format-length { ok-points }
ext-ALPN-length { ok-ALPN }
begin dup while
read16
case
@ -753,6 +803,15 @@ cc: DEBUG-BLOB ( addr len -- ) {
read-ignore-16
endof
\ ALPN.
0x0010 of
ok-ALPN ifnot
ERR_EXTRA_EXTENSION fail
then
0 >ok-ALPN
read-ALPN-from-server
endof
ERR_EXTRA_EXTENSION fail
endcase
repeat
@ -1102,6 +1161,7 @@ cc: do-client-sign ( -- sig_len ) {
: do-handshake ( -- )
0 addr-application_data set8
22 addr-record_type_out set8
0 addr-selected_protocol set16
multihash-init
write-ClientHello

View file

@ -157,6 +157,8 @@ addr-eng: pad
addr-eng: action
addr-eng: alert
addr-eng: close_received
addr-eng: protocol_names_num
addr-eng: selected_protocol
\ Similar to 'addr-eng:', for fields in the 'session' substructure.
: addr-session-field:
@ -1235,3 +1237,33 @@ cc: get-key-type-usages ( -- key-type-usages ) {
\ Return key type and usages.
get-key-type-usages ;
\ =======================================================================
\ Copy a specific protocol name from the list to the pad. The byte
\ length is returned.
cc: copy-protocol-name ( idx -- len ) {
size_t idx = T0_POP();
size_t len = strlen(ENG->protocol_names[idx]);
memcpy(ENG->pad, ENG->protocol_names[idx], len);
T0_PUSH(len);
}
\ Compare name in pad with the configured list of protocol names.
\ If a match is found, then the index is returned; otherwise, -1
\ is returned.
cc: test-protocol-name ( len -- n ) {
size_t len = T0_POP();
size_t u;
for (u = 0; u < ENG->protocol_names_num; u ++) {
const char *name;
name = ENG->protocol_names[u];
if (len == strlen(name) && memcmp(ENG->pad, name, len) == 0) {
T0_PUSH(u);
T0_RET();
}
}
T0_PUSHi(-1);
}

File diff suppressed because it is too large Load diff

View file

@ -528,6 +528,41 @@ cc: set-max-frag-len ( len -- ) {
close-elt
close-elt ;
\ Read the ALPN extension from client.
: read-ALPN-from-client ( lim -- lim )
\ If we do not have configured names, then we just ignore the
\ extension.
addr-protocol_names_num get16 ifnot read-ignore-16 ret then
\ Open extension value.
read16 open-elt
\ Open list of protocol names.
read16 open-elt
\ Get all names and test for their support. We keep the one with
\ the lowest index (because we apply server's preferences, as
\ recommended by RFC 7301, section 3.2. We set the 'found' variable
\ to -2 and use an unsigned comparison, making -2 a huge value.
-2 { found }
begin dup while
read8 dup { len } addr-pad swap read-blob
len test-protocol-name dup found u< if
>found
else
drop
then
repeat
\ End of extension.
close-elt
close-elt
\ Write back found name index (or not). If no match was found,
\ then we write -1 (0xFFFF) in the index value, not 0, so that
\ the caller knows that we tried to match, and failed.
found 1+ addr-selected_protocol set16 ;
\ Call policy handler to get cipher suite, hash function identifier and
\ certificate chain. Returned value is 0 (false) on failure.
cc: call-policy-handler ( -- bool ) {
@ -709,6 +744,11 @@ cc: save-session ( -- ) {
\ read-ignore-16
\ endof
\ ALPN
0x0010 of
read-ALPN-from-client
endof
\ Other extensions are ignored.
drop read-ignore-16 0
endcase
@ -832,6 +872,12 @@ cc: save-session ( -- ) {
then
addr-client_suites_num set8
\ Check ALPN.
addr-selected_protocol get16 0xFFFF = if
3 flag? if 120 fail-alert then
0 addr-selected_protocol set16
then
\ Call policy handler to obtain the cipher suite and other
\ parameters.
call-policy-handler ifnot 40 fail-alert then
@ -842,20 +888,28 @@ cc: save-session ( -- ) {
\ Write ServerHello.
: write-ServerHello ( initial -- )
{ initial }
\ Compute ServerHello length. Right now we only send the
\ "secure renegotiation" extension.
\ Compute ServerHello length.
2 write8 70
\ Compute length of Secure Renegotiation extension.
addr-reneg get8 2 = if
initial if 5 else 29 then
else
0
then
{ ext-reneg-len }
\ Compute length of Max Fragment Length extension.
addr-peer_log_max_frag_len get8 if 5 else 0 then
{ ext-max-frag-len }
ext-reneg-len ext-max-frag-len + dup if 2 + then +
\ Compute length of ALPN extension. This also copy the
\ selected protocol name into the pad.
addr-selected_protocol get16 dup if 1- copy-protocol-name 7 + then
{ ext-ALPN-len }
\ Adjust ServerHello length to account for the extensions.
ext-reneg-len ext-max-frag-len + ext-ALPN-len + dup if 2 + then +
write24
\ Protocol version
@ -880,7 +934,7 @@ cc: save-session ( -- ) {
0 write8
\ Extensions
ext-reneg-len ext-max-frag-len + dup if
ext-reneg-len ext-max-frag-len + ext-ALPN-len + dup if
write16
ext-reneg-len dup if
0xFF01 write16
@ -893,6 +947,16 @@ cc: save-session ( -- ) {
0x0001 write16
1 write16 addr-peer_log_max_frag_len get8 8 - write8
then
ext-ALPN-len dup if
\ Note: the selected protocol name was previously
\ copied into the pad.
0x0010 write16
4 - dup write16
2- dup write16
1- addr-pad swap write-blob-head8
else
drop
then
else
drop
then ;
@ -1289,6 +1353,7 @@ cc: verify-CV-sig ( sig-len -- err ) {
: do-handshake ( initial -- )
0 addr-application_data set8
22 addr-record_type_out set8
0 addr-selected_protocol set16
multihash-init
read-ClientHello
more-incoming-bytes? if ERR_UNEXPECTED fail then

View file

@ -419,6 +419,12 @@ static const br_ssl_client_certificate_class ccert_vtable = {
cc_do_sign
};
static void
free_alpn(void *alpn)
{
xfree(*(char **)alpn);
}
static void
usage_client(void)
{
@ -462,6 +468,10 @@ usage_client(void)
" -fallback send the TLS_FALLBACK_SCSV (i.e. claim a downgrade)\n");
fprintf(stderr,
" -noreneg prohibit renegotiations\n");
fprintf(stderr,
" -alpn name add protocol name to list of protocols (ALPN extension)\n");
fprintf(stderr,
" -strictalpn fail on ALPN mismatch\n");
}
/* see brssl.h */
@ -478,6 +488,7 @@ do_client(int argc, char *argv[])
const char *sni;
anchor_list anchors = VEC_INIT;
unsigned vmin, vmax;
VECTOR(const char *) alpn_names = VEC_INIT;
cipher_suite *suites;
size_t num_suites;
uint16_t *suite_ids;
@ -744,6 +755,16 @@ do_client(int argc, char *argv[])
fallback = 1;
} else if (eqstr(arg, "-noreneg")) {
flags |= BR_OPT_NO_RENEGOTIATION;
} else if (eqstr(arg, "-alpn")) {
if (++ i >= argc) {
fprintf(stderr,
"ERROR: no argument for '-alpn'\n");
usage_client();
goto client_exit_error;
}
VEC_ADD(alpn_names, xstrdup(argv[i]));
} else if (eqstr(arg, "-strictalpn")) {
flags |= BR_OPT_FAIL_ON_ALPN_MISMATCH;
} else {
fprintf(stderr, "ERROR: unknown option: '%s'\n", arg);
usage_client();
@ -1002,6 +1023,10 @@ do_client(int argc, char *argv[])
br_ssl_client_set_min_clienthello_len(&cc, minhello_len);
}
br_ssl_engine_set_all_flags(&cc.eng, flags);
if (VEC_LEN(alpn_names) != 0) {
br_ssl_engine_set_protocol_names(&cc.eng,
&VEC_ELT(alpn_names, 0), VEC_LEN(alpn_names));
}
if (chain != NULL) {
zc.vtable = &ccert_vtable;
@ -1057,6 +1082,7 @@ client_exit:
xfree(suites);
xfree(suite_ids);
VEC_CLEAREXT(anchors, &free_ta_contents);
VEC_CLEAREXT(alpn_names, &free_alpn);
free_certificates(chain, chain_len);
free_private_key(sk);
xfree(iobuf);

View file

@ -229,6 +229,12 @@ usage_server(void)
" -hf names add support for some hash functions (comma-separated)\n");
fprintf(stderr,
" -serverpref enforce server's preferences for cipher suites\n");
fprintf(stderr,
" -noreneg prohibit renegotiations\n");
fprintf(stderr,
" -alpn name add protocol name to list of protocols (ALPN extension)\n");
fprintf(stderr,
" -strictalpn fail on ALPN mismatch\n");
exit(EXIT_FAILURE);
}
@ -512,6 +518,12 @@ static const br_ssl_server_policy_class policy_vtable = {
sp_do_sign
};
void
free_alpn(void *alpn)
{
xfree(*(char **)alpn);
}
/* see brssl.h */
int
do_server(int argc, char *argv[])
@ -532,6 +544,7 @@ do_server(int argc, char *argv[])
int cert_signer_algo;
private_key *sk;
anchor_list anchors = VEC_INIT;
VECTOR(const char *) alpn_names = VEC_INIT;
br_x509_minimal_context xc;
const br_hash_class *dnhash;
size_t u;
@ -781,6 +794,16 @@ do_server(int argc, char *argv[])
flags |= BR_OPT_ENFORCE_SERVER_PREFERENCES;
} else if (eqstr(arg, "-noreneg")) {
flags |= BR_OPT_NO_RENEGOTIATION;
} else if (eqstr(arg, "-alpn")) {
if (++ i >= argc) {
fprintf(stderr,
"ERROR: no argument for '-alpn'\n");
usage_server();
goto server_exit_error;
}
VEC_ADD(alpn_names, xstrdup(argv[i]));
} else if (eqstr(arg, "-strictalpn")) {
flags |= BR_OPT_FAIL_ON_ALPN_MISMATCH;
} else {
fprintf(stderr, "ERROR: unknown option: '%s'\n", arg);
usage_server();
@ -999,6 +1022,11 @@ do_server(int argc, char *argv[])
br_ssl_session_cache_lru_init(&lru, cache, cache_len);
br_ssl_server_set_cache(&cc, &lru.vtable);
if (VEC_LEN(alpn_names) != 0) {
br_ssl_engine_set_protocol_names(&cc.eng,
&VEC_ELT(alpn_names, 0), VEC_LEN(alpn_names));
}
/*
* Set the policy handler (that chooses the actual cipher suite,
* selects the certificate chain, and runs the private key
@ -1091,6 +1119,7 @@ server_exit:
free_certificates(chain, chain_len);
free_private_key(sk);
VEC_CLEAREXT(anchors, &free_ta_contents);
VEC_CLEAREXT(alpn_names, &free_alpn);
xfree(iobuf);
xfree(cache);
if (fd >= 0) {

View file

@ -213,6 +213,7 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
recvapp = ((st & BR_SSL_RECVAPP) != 0);
if (verbose && sendapp && !hsdetails) {
char csn[80];
const char *pname;
fprintf(stderr, "Handshake completed\n");
fprintf(stderr, " version: ");
@ -240,6 +241,12 @@ run_ssl_engine(br_ssl_engine_context *cc, int fd, unsigned flags)
fprintf(stderr, " cipher suite: %s\n", csn);
fprintf(stderr, " secure renegotiation: %s\n",
cc->reneg == 1 ? "no" : "yes");
pname = br_ssl_engine_get_selected_protocol(cc);
if (pname != NULL) {
fprintf(stderr,
" protocol name (ALPN): %s\n",
pname);
}
hsdetails = 1;
}