summaryrefslogtreecommitdiffstats
path: root/ssl
diff options
context:
space:
mode:
authorAdam Langley <agl@chromium.org>2013-04-15 18:07:47 -0400
committerAdam Langley <agl@chromium.org>2013-09-13 11:27:22 -0400
commitb0d6f3c58fc86756574b410cb6a32589477d3954 (patch)
tree2a7d1dec7ff22974b355b4ffebfbf6fdaeea1a3b /ssl
parent033864842607895730d97baf4103da24f1207762 (diff)
Support ALPN.
This change adds support for ALPN[1] in OpenSSL. ALPN is the IETF blessed version of NPN and we'll be supporting both ALPN and NPN for some time yet. Cherry-picked from 6f017a8f9db3a79f3a3406cf8d493ccd346db691. [1] https://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-00
Diffstat (limited to 'ssl')
-rw-r--r--ssl/s3_lib.c13
-rw-r--r--ssl/ssl.h45
-rw-r--r--ssl/ssl3.h12
-rw-r--r--ssl/ssl_lib.c88
-rw-r--r--ssl/t1_lib.c168
-rw-r--r--ssl/tls1.h3
6 files changed, 326 insertions, 3 deletions
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index f3acb8a96a..601168126b 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -3002,6 +3002,11 @@ void ssl3_free(SSL *s)
BIO_free(s->s3->handshake_buffer);
}
if (s->s3->handshake_dgst) ssl3_free_digest_list(s);
+#ifndef OPENSSL_NO_TLSEXT
+ if (s->s3->alpn_selected)
+ OPENSSL_free(s->s3->alpn_selected);
+#endif
+
#ifndef OPENSSL_NO_SRP
SSL_SRP_CTX_free(s);
#endif
@@ -3080,6 +3085,14 @@ void ssl3_clear(SSL *s)
if (s->s3->handshake_dgst) {
ssl3_free_digest_list(s);
}
+
+#if !defined(OPENSSL_NO_TLSEXT)
+ if (s->s3->alpn_selected)
+ {
+ free(s->s3->alpn_selected);
+ s->s3->alpn_selected = NULL;
+ }
+#endif
memset(s->s3,0,sizeof *s->s3);
s->s3->rbuf.buf = rp;
s->s3->wbuf.buf = wp;
diff --git a/ssl/ssl.h b/ssl/ssl.h
index bd2b576308..700e2c3e02 100644
--- a/ssl/ssl.h
+++ b/ssl/ssl.h
@@ -1097,6 +1097,31 @@ struct ssl_ctx_st
void *arg);
void *next_proto_select_cb_arg;
# endif
+
+ /* ALPN information
+ * (we are in the process of transitioning from NPN to ALPN.) */
+
+ /* For a server, this contains a callback function that allows the
+ * server to select the protocol for the connection.
+ * out: on successful return, this must point to the raw protocol
+ * name (without the length prefix).
+ * outlen: on successful return, this contains the length of |*out|.
+ * in: points to the client's list of supported protocols in
+ * wire-format.
+ * inlen: the length of |in|. */
+ int (*alpn_select_cb)(SSL *s,
+ const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char* in,
+ unsigned int inlen,
+ void *arg);
+ void *alpn_select_cb_arg;
+
+ /* For a client, this contains the list of supported protocols in wire
+ * format. */
+ unsigned char* alpn_client_proto_list;
+ unsigned alpn_client_proto_list_len;
+
/* SRTP profiles we are willing to do from RFC 5764 */
STACK_OF(SRTP_PROTECTION_PROFILE) *srtp_profiles;
# ifndef OPENSSL_NO_EC
@@ -1195,6 +1220,21 @@ void SSL_get0_next_proto_negotiated(const SSL *s,
#define OPENSSL_NPN_NO_OVERLAP 2
#endif
+int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char* protos,
+ unsigned protos_len);
+int SSL_set_alpn_protos(SSL *ssl, const unsigned char* protos,
+ unsigned protos_len);
+void SSL_CTX_set_alpn_select_cb(SSL_CTX* ctx,
+ int (*cb) (SSL *ssl,
+ const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg),
+ void *arg);
+void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
+ unsigned *len);
+
#ifndef OPENSSL_NO_PSK
/* the maximum length of the buffer given to callbacks containing the
* resulting identity/psk */
@@ -1501,6 +1541,11 @@ struct ssl_st
*/
unsigned int tlsext_hb_pending; /* Indicates if a HeartbeatRequest is in flight */
unsigned int tlsext_hb_seq; /* HeartbeatRequest sequence number */
+
+ /* For a client, this contains the list of supported protocols in wire
+ * format. */
+ unsigned char* alpn_client_proto_list;
+ unsigned alpn_client_proto_list_len;
#else
#define session_ctx ctx
#endif /* OPENSSL_NO_TLSEXT */
diff --git a/ssl/ssl3.h b/ssl/ssl3.h
index 171c76a73c..05317005de 100644
--- a/ssl/ssl3.h
+++ b/ssl/ssl3.h
@@ -580,7 +580,17 @@ typedef struct ssl3_state_st
* as the types were received in the client hello. */
unsigned short *tlsext_custom_types;
size_t tlsext_custom_types_count; /* how many tlsext_custom_types */
-#endif
+
+ /* ALPN information
+ * (we are in the process of transitioning from NPN to ALPN.) */
+
+ /* In a server these point to the selected ALPN protocol after the
+ * ClientHello has been processed. In a client these contain the
+ * protocol that the server selected once the ServerHello has been
+ * processed. */
+ unsigned char *alpn_selected;
+ unsigned alpn_selected_len;
+#endif /* OPENSSL_NO_TLSEXT */
} SSL3_STATE;
#endif
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index dad33fa14b..aaf6a4cdfd 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -381,6 +381,17 @@ SSL *SSL_new(SSL_CTX *ctx)
# ifndef OPENSSL_NO_NEXTPROTONEG
s->next_proto_negotiated = NULL;
# endif
+
+ if (s->ctx->alpn_client_proto_list)
+ {
+ s->alpn_client_proto_list =
+ OPENSSL_malloc(s->ctx->alpn_client_proto_list_len);
+ if (s->alpn_client_proto_list == NULL)
+ goto err;
+ memcpy(s->alpn_client_proto_list, s->ctx->alpn_client_proto_list,
+ s->ctx->alpn_client_proto_list_len);
+ s->alpn_client_proto_list_len = s->ctx->alpn_client_proto_list_len;
+ }
#endif
s->verify_result=X509_V_OK;
@@ -605,6 +616,8 @@ void SSL_free(SSL *s)
sk_OCSP_RESPID_pop_free(s->tlsext_ocsp_ids, OCSP_RESPID_free);
if (s->tlsext_ocsp_resp)
OPENSSL_free(s->tlsext_ocsp_resp);
+ if (s->alpn_client_proto_list)
+ OPENSSL_free(s->alpn_client_proto_list);
#endif
if (s->client_CA != NULL)
@@ -1769,7 +1782,78 @@ int SSL_CTX_set_custom_srv_ext(SSL_CTX *ctx, unsigned short ext_type,
return 1;
}
-#endif
+/* SSL_CTX_set_alpn_protos sets the ALPN protocol list on |ctx| to |protos|.
+ * |protos| must be in wire-format (i.e. a series of non-empty, 8-bit
+ * length-prefixed strings).
+ *
+ * Returns 0 on success. */
+int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char* protos,
+ unsigned protos_len)
+ {
+ if (ctx->alpn_client_proto_list)
+ OPENSSL_free(ctx->alpn_client_proto_list);
+
+ ctx->alpn_client_proto_list = OPENSSL_malloc(protos_len);
+ if (!ctx->alpn_client_proto_list)
+ return 1;
+ memcpy(ctx->alpn_client_proto_list, protos, protos_len);
+ ctx->alpn_client_proto_list_len = protos_len;
+
+ return 0;
+ }
+
+/* SSL_set_alpn_protos sets the ALPN protocol list on |ssl| to |protos|.
+ * |protos| must be in wire-format (i.e. a series of non-empty, 8-bit
+ * length-prefixed strings).
+ *
+ * Returns 0 on success. */
+int SSL_set_alpn_protos(SSL *ssl, const unsigned char* protos,
+ unsigned protos_len)
+ {
+ if (ssl->alpn_client_proto_list)
+ OPENSSL_free(ssl->alpn_client_proto_list);
+
+ ssl->alpn_client_proto_list = OPENSSL_malloc(protos_len);
+ if (!ssl->alpn_client_proto_list)
+ return 1;
+ memcpy(ssl->alpn_client_proto_list, protos, protos_len);
+ ssl->alpn_client_proto_list_len = protos_len;
+
+ return 0;
+ }
+
+/* SSL_CTX_set_alpn_select_cb sets a callback function on |ctx| that is called
+ * during ClientHello processing in order to select an ALPN protocol from the
+ * client's list of offered protocols. */
+void SSL_CTX_set_alpn_select_cb(SSL_CTX* ctx,
+ int (*cb) (SSL *ssl,
+ const unsigned char **out,
+ unsigned char *outlen,
+ const unsigned char *in,
+ unsigned int inlen,
+ void *arg),
+ void *arg)
+ {
+ ctx->alpn_select_cb = cb;
+ ctx->alpn_select_cb_arg = arg;
+ }
+
+/* SSL_get0_alpn_selected gets the selected ALPN protocol (if any) from |ssl|.
+ * On return it sets |*data| to point to |*len| bytes of protocol name (not
+ * including the leading length-prefix byte). If the server didn't respond with
+ * a negotiated protocol then |*len| will be zero. */
+void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
+ unsigned *len)
+ {
+ *data = NULL;
+ if (ssl->s3)
+ *data = ssl->s3->alpn_selected;
+ if (*data == NULL)
+ *len = 0;
+ else
+ *len = ssl->s3->alpn_selected_len;
+ }
+#endif /* !OPENSSL_NO_TLSEXT */
int SSL_export_keying_material(SSL *s, unsigned char *out, size_t olen,
const char *label, size_t llen, const unsigned char *p, size_t plen,
@@ -2132,6 +2216,8 @@ void SSL_CTX_free(SSL_CTX *a)
if (a->tlsext_ellipticcurvelist)
OPENSSL_free(a->tlsext_ellipticcurvelist);
# endif /* OPENSSL_NO_EC */
+ if (a->alpn_client_proto_list != NULL)
+ OPENSSL_free(a->alpn_client_proto_list);
#endif
OPENSSL_free(a);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index ea10a52f91..41010ee647 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1371,6 +1371,18 @@ unsigned char *ssl_add_clienthello_tlsext(SSL *s, unsigned char *p, unsigned cha
}
#endif
+ if (s->alpn_client_proto_list && !s->s3->tmp.finish_md_len)
+ {
+ if ((size_t)(limit - ret) < 6 + s->alpn_client_proto_list_len)
+ return NULL;
+ s2n(TLSEXT_TYPE_application_layer_protocol_negotiation,ret);
+ s2n(2 + s->alpn_client_proto_list_len,ret);
+ s2n(s->alpn_client_proto_list_len,ret);
+ memcpy(ret, s->alpn_client_proto_list,
+ s->alpn_client_proto_list_len);
+ ret += s->alpn_client_proto_list_len;
+ }
+
if(SSL_get_srtp_profiles(s))
{
int el;
@@ -1753,6 +1765,21 @@ unsigned char *ssl_add_serverhello_tlsext(SSL *s, unsigned char *p, unsigned cha
}
}
+ if (s->s3->alpn_selected)
+ {
+ const unsigned char *selected = s->s3->alpn_selected;
+ unsigned len = s->s3->alpn_selected_len;
+
+ if ((long)(limit - ret - 4 - 2 - 1 - len) < 0)
+ return NULL;
+ s2n(TLSEXT_TYPE_application_layer_protocol_negotiation,ret);
+ s2n(3 + len,ret);
+ s2n(1 + len,ret);
+ *ret++ = len;
+ memcpy(ret, selected, len);
+ ret += len;
+ }
+
if ((extdatalen = ret-p-2)== 0)
return p;
@@ -1760,6 +1787,76 @@ unsigned char *ssl_add_serverhello_tlsext(SSL *s, unsigned char *p, unsigned cha
return ret;
}
+/* tls1_alpn_handle_client_hello is called to process the ALPN extension in a
+ * ClientHello.
+ * data: the contents of the extension, not including the type and length.
+ * data_len: the number of bytes in |data|
+ * al: a pointer to the alert value to send in the event of a non-zero
+ * return.
+ *
+ * returns: 0 on success. */
+static int tls1_alpn_handle_client_hello(SSL *s, const unsigned char *data,
+ unsigned data_len, int *al)
+ {
+ unsigned i;
+ unsigned proto_len;
+ const unsigned char *selected;
+ unsigned char selected_len;
+ int r;
+
+ if (s->ctx->alpn_select_cb == NULL)
+ return 0;
+
+ if (data_len < 2)
+ goto parse_error;
+
+ /* data should contain a uint16 length followed by a series of 8-bit,
+ * length-prefixed strings. */
+ i = ((unsigned) data[0]) << 8 |
+ ((unsigned) data[1]);
+ data_len -= 2;
+ data += 2;
+ if (data_len != i)
+ goto parse_error;
+
+ if (data_len < 2)
+ goto parse_error;
+
+ for (i = 0; i < data_len;)
+ {
+ proto_len = data[i];
+ i++;
+
+ if (proto_len == 0)
+ goto parse_error;
+
+ if (i + proto_len < i || i + proto_len > data_len)
+ goto parse_error;
+
+ i += proto_len;
+ }
+
+ r = s->ctx->alpn_select_cb(s, &selected, &selected_len, data, data_len,
+ s->ctx->alpn_select_cb_arg);
+ if (r == SSL_TLSEXT_ERR_OK) {
+ if (s->s3->alpn_selected)
+ OPENSSL_free(s->s3->alpn_selected);
+ s->s3->alpn_selected = OPENSSL_malloc(selected_len);
+ if (!s->s3->alpn_selected)
+ {
+ *al = SSL_AD_INTERNAL_ERROR;
+ return -1;
+ }
+ memcpy(s->s3->alpn_selected, selected, selected_len);
+ s->s3->alpn_selected_len = selected_len;
+ }
+ return 0;
+
+parse_error:
+ *al = SSL_AD_DECODE_ERROR;
+ return -1;
+ }
+
static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char *d, int n, int *al)
{
unsigned short type;
@@ -1775,6 +1872,12 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
s->s3->next_proto_neg_seen = 0;
#endif
+ if (s->s3->alpn_selected)
+ {
+ OPENSSL_free(s->s3->alpn_selected);
+ s->s3->alpn_selected = NULL;
+ }
+
#ifndef OPENSSL_NO_HEARTBEATS
s->tlsext_heartbeat &= ~(SSL_TLSEXT_HB_ENABLED |
SSL_TLSEXT_HB_DONT_SEND_REQUESTS);
@@ -2233,7 +2336,8 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
#endif
#ifndef OPENSSL_NO_NEXTPROTONEG
else if (type == TLSEXT_TYPE_next_proto_neg &&
- s->s3->tmp.finish_md_len == 0)
+ s->s3->tmp.finish_md_len == 0 &&
+ s->s3->alpn_selected == NULL)
{
/* We shouldn't accept this extension on a
* renegotiation.
@@ -2254,6 +2358,16 @@ static int ssl_scan_clienthello_tlsext(SSL *s, unsigned char **p, unsigned char
}
#endif
+ else if (type == TLSEXT_TYPE_application_layer_protocol_negotiation &&
+ s->ctx->alpn_select_cb &&
+ s->s3->tmp.finish_md_len == 0)
+ {
+ if (tls1_alpn_handle_client_hello(s, data, size, al) != 0)
+ return 0;
+ /* ALPN takes precedence over NPN. */
+ s->s3->next_proto_neg_seen = 0;
+ }
+
/* session ticket processed earlier */
else if (type == TLSEXT_TYPE_use_srtp)
{
@@ -2443,6 +2557,12 @@ static int ssl_scan_serverhello_tlsext(SSL *s, unsigned char **p, unsigned char
s->s3->next_proto_neg_seen = 0;
#endif
+ if (s->s3->alpn_selected)
+ {
+ OPENSSL_free(s->s3->alpn_selected);
+ s->s3->alpn_selected = NULL;
+ }
+
#ifndef OPENSSL_NO_HEARTBEATS
s->tlsext_heartbeat &= ~(SSL_TLSEXT_HB_ENABLED |
SSL_TLSEXT_HB_DONT_SEND_REQUESTS);
@@ -2608,6 +2728,52 @@ static int ssl_scan_serverhello_tlsext(SSL *s, unsigned char **p, unsigned char
s->s3->next_proto_neg_seen = 1;
}
#endif
+
+ else if (type == TLSEXT_TYPE_application_layer_protocol_negotiation)
+ {
+ unsigned len;
+
+ /* We must have requested it. */
+ if (s->alpn_client_proto_list == NULL)
+ {
+ *al = TLS1_AD_UNSUPPORTED_EXTENSION;
+ return 0;
+ }
+ if (size < 4)
+ {
+ *al = TLS1_AD_DECODE_ERROR;
+ return 0;
+ }
+ /* The extension data consists of:
+ * uint16 list_length
+ * uint8 proto_length;
+ * uint8 proto[proto_length]; */
+ len = data[0];
+ len <<= 8;
+ len |= data[1];
+ if (len != (unsigned) size - 2)
+ {
+ *al = TLS1_AD_DECODE_ERROR;
+ return 0;
+ }
+ len = data[2];
+ if (len != (unsigned) size - 3)
+ {
+ *al = TLS1_AD_DECODE_ERROR;
+ return 0;
+ }
+ if (s->s3->alpn_selected)
+ OPENSSL_free(s->s3->alpn_selected);
+ s->s3->alpn_selected = OPENSSL_malloc(len);
+ if (!s->s3->alpn_selected)
+ {
+ *al = TLS1_AD_INTERNAL_ERROR;
+ return 0;
+ }
+ memcpy(s->s3->alpn_selected, data + 3, len);
+ s->s3->alpn_selected_len = len;
+ }
+
else if (type == TLSEXT_TYPE_renegotiate)
{
if(!ssl_parse_serverhello_renegotiate_ext(s, data, size, al))
diff --git a/ssl/tls1.h b/ssl/tls1.h
index c59a02fee3..b1b85bf632 100644
--- a/ssl/tls1.h
+++ b/ssl/tls1.h
@@ -230,6 +230,9 @@ extern "C" {
/* ExtensionType value from RFC5620 */
#define TLSEXT_TYPE_heartbeat 15
+/* ExtensionType value from draft-ietf-tls-applayerprotoneg-00 */
+#define TLSEXT_TYPE_application_layer_protocol_negotiation 16
+
/* ExtensionType value from RFC4507 */
#define TLSEXT_TYPE_session_ticket 35