summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Smith <brian@briansmith.org>2014-01-15 21:20:30 -0800
committerBrian Smith <brian@briansmith.org>2014-01-15 21:20:30 -0800
commit80caf801b2c1fce41362b54af8180a34a7751646 (patch)
treef0bf7ba8bcd9cd9839e5128c7e24ad9dbe241cea
parent67e3b1581c554a47f197b5c2bdbccee87336277a (diff)
downloadnss-hg-80caf801b2c1fce41362b54af8180a34a7751646.tar.gz
Bug 959664: Add ALPN support to NSS, r=briansmithNSS_3_15_5_BETA2
-rw-r--r--lib/ssl/ssl.h32
-rw-r--r--lib/ssl/ssl3con.c4
-rw-r--r--lib/ssl/ssl3ext.c123
-rw-r--r--lib/ssl/sslimpl.h2
-rw-r--r--lib/ssl/sslsock.c24
-rw-r--r--lib/ssl/sslt.h3
6 files changed, 183 insertions, 5 deletions
diff --git a/lib/ssl/ssl.h b/lib/ssl/ssl.h
index 79987e69a..b27af6658 100644
--- a/lib/ssl/ssl.h
+++ b/lib/ssl/ssl.h
@@ -162,6 +162,25 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd);
#define SSL_CBC_RANDOM_IV 23
#define SSL_ENABLE_OCSP_STAPLING 24 /* Request OCSP stapling (client) */
+/* SSL_ENABLE_NPN controls whether the NPN extension is enabled for the initial
+ * handshake when protocol negotiation is used. SSL_SetNextProtoCallback
+ * or SSL_SetNextProtoNego must be used to control the protocol negotiation;
+ * otherwise, the NPN extension will not be negotiated. SSL_ENABLE_NPN is
+ * currently enabled by default but this may change in future versions.
+ */
+#define SSL_ENABLE_NPN 25
+
+/* SSL_ENABLE_ALPN controls whether the ALPN extension is enabled for the
+ * initial handshake when protocol negotiation is used. SSL_SetNextProtoNego
+ * (not SSL_SetNextProtoCallback) must be used to control the protocol
+ * negotiation; otherwise, the ALPN extension will not be negotiated. ALPN is
+ * not negotiated for renegotiation handshakes, even though the ALPN
+ * specification defines a way to use ALPN during renegotiations.
+ * SSL_ENABLE_ALPN is currently disabled by default, but this may change in
+ * future versions.
+ */
+#define SSL_ENABLE_ALPN 26
+
#ifdef SSL_DEPRECATED_FUNCTION
/* Old deprecated function names */
SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRBool on);
@@ -206,6 +225,16 @@ SSL_IMPORT SECStatus SSL_SetNextProtoCallback(PRFileDesc *fd,
* protocol in server-preference order. If no matching protocol is found it
* selects the first supported protocol.
*
+ * Using this function also allows the client to transparently support ALPN.
+ * The same set of protocols will be advertised via ALPN and, if the server
+ * uses ALPN to select a protocol, SSL_GetNextProto will return
+ * SSL_NEXT_PROTO_SELECTED as the state.
+ *
+ * Since NPN uses the first protocol as the fallback protocol, when sending an
+ * ALPN extension, the first protocol is moved to the end of the list. This
+ * indicates that the fallback protocol is the least preferred. The other
+ * protocols should be in preference order.
+ *
* The supported protocols are specified in |data| in wire-format (8-bit
* length-prefixed). For example: "\010http/1.1\006spdy/2". */
SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd,
@@ -215,7 +244,8 @@ SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd,
typedef enum SSLNextProtoState {
SSL_NEXT_PROTO_NO_SUPPORT = 0, /* No peer support */
SSL_NEXT_PROTO_NEGOTIATED = 1, /* Mutual agreement */
- SSL_NEXT_PROTO_NO_OVERLAP = 2 /* No protocol overlap found */
+ SSL_NEXT_PROTO_NO_OVERLAP = 2, /* No protocol overlap found */
+ SSL_NEXT_PROTO_SELECTED = 3 /* Server selected proto (ALPN) */
} SSLNextProtoState;
/* SSL_GetNextProto can be used in the HandshakeCallback or any time after
diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c
index 9e8d2e41a..cc3030612 100644
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -10185,8 +10185,10 @@ ssl3_SendNextProto(sslSocket *ss)
int padding_len;
static const unsigned char padding[32] = {0};
- if (ss->ssl3.nextProto.len == 0)
+ if (ss->ssl3.nextProto.len == 0 ||
+ ss->ssl3.nextProtoState == SSL_NEXT_PROTO_SELECTED) {
return SECSuccess;
+ }
PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c
index d75d458b6..792a3b421 100644
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -52,8 +52,12 @@ static SECStatus ssl3_HandleRenegotiationInfoXtn(sslSocket *ss,
PRUint16 ex_type, SECItem *data);
static SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss,
PRUint16 ex_type, SECItem *data);
+static SECStatus ssl3_ClientHandleAppProtoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
static SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss,
PRUint16 ex_type, SECItem *data);
+static PRInt32 ssl3_ClientSendAppProtoXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
static PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append,
PRUint32 maxBytes);
static PRInt32 ssl3_SendUseSRTPXtn(sslSocket *ss, PRBool append,
@@ -247,6 +251,7 @@ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
{ ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
{ ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
{ ssl_next_proto_nego_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
+ { ssl_app_layer_protocol_xtn, &ssl3_ClientHandleAppProtoXtn },
{ ssl_use_srtp_xtn, &ssl3_HandleUseSRTPXtn },
{ ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
{ -1, NULL }
@@ -273,6 +278,7 @@ ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
#endif
{ ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
{ ssl_next_proto_nego_xtn, &ssl3_ClientSendNextProtoNegoXtn },
+ { ssl_app_layer_protocol_xtn, &ssl3_ClientSendAppProtoXtn },
{ ssl_use_srtp_xtn, &ssl3_SendUseSRTPXtn },
{ ssl_cert_status_xtn, &ssl3_ClientSendStatusRequestXtn },
{ ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn }
@@ -608,6 +614,16 @@ ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
PORT_Assert(!ss->firstHsDone);
+ if (ssl3_ExtensionNegotiated(ss, ssl_app_layer_protocol_xtn)) {
+ /* If the server negotiated ALPN then it has already told us what protocol
+ * to use, so it doesn't make sense for us to try to negotiate a different
+ * one by sending the NPN handshake message. However, if we've negotiated
+ * NPN then we're required to send the NPN handshake message. Thus, these
+ * two extensions cannot both be negotiated on the same connection. */
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
rv = ssl3_ValidateNextProtoNego(data->data, data->len);
if (rv != SECSuccess)
return rv;
@@ -641,6 +657,43 @@ ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &result);
}
+static SECStatus
+ssl3_ClientHandleAppProtoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
+{
+ const unsigned char* d = data->data;
+ PRUint16 name_list_len;
+ SECItem protocol_name;
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_next_proto_nego_xtn)) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ /* The extension data from the server has the following format:
+ * uint16 name_list_len;
+ * uint8 len;
+ * uint8 protocol_name[len]; */
+ if (data->len < 4 || data->len > 2 + 1 + 255) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ name_list_len = ((PRUint16) d[0]) << 8 |
+ ((PRUint16) d[1]);
+ if (name_list_len != data->len - 2 || d[2] != data->len - 3) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ protocol_name.data = data->data + 3;
+ protocol_name.len = data->len - 3;
+
+ SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE);
+ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_SELECTED;
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &protocol_name);
+}
+
static PRInt32
ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, PRBool append,
PRUint32 maxBytes)
@@ -648,7 +701,7 @@ ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, PRBool append,
PRInt32 extension_length;
/* Renegotiations do not send this extension. */
- if (!ss->nextProtoCallback || ss->firstHsDone) {
+ if (!ss->opt.enableNPN || !ss->nextProtoCallback || ss->firstHsDone) {
return 0;
}
@@ -674,6 +727,74 @@ loser:
return -1;
}
+static PRInt32
+ssl3_ClientSendAppProtoXtn(sslSocket * ss, PRBool append, PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+ unsigned char *alpn_protos = NULL;
+
+ /* Renegotiations do not send this extension. */
+ if (!ss->opt.enableALPN || !ss->opt.nextProtoNego.data || ss->firstHsDone) {
+ return 0;
+ }
+
+ extension_length = 2 /* extension type */ + 2 /* extension length */ +
+ 2 /* protocol name list length */ +
+ ss->opt.nextProtoNego.len;
+
+ if (append && maxBytes >= extension_length) {
+ /* NPN requires that the client's fallback protocol is first in the
+ * list. However, ALPN sends protocols in preference order. So we
+ * allocate a buffer and move the first protocol to the end of the
+ * list. */
+ SECStatus rv;
+ const unsigned int len = ss->opt.nextProtoNego.len;
+
+ alpn_protos = PORT_Alloc(len);
+ if (alpn_protos == NULL) {
+ return SECFailure;
+ }
+ if (len > 0) {
+ /* Each protocol string is prefixed with a single byte length. */
+ unsigned int i = ss->opt.nextProtoNego.data[0] + 1;
+ if (i <= len) {
+ memcpy(alpn_protos, &ss->opt.nextProtoNego.data[i], len - i);
+ memcpy(alpn_protos + len - i, ss->opt.nextProtoNego.data, i);
+ } else {
+ /* This seems to be invalid data so we'll send as-is. */
+ memcpy(alpn_protos, ss->opt.nextProtoNego.data, len);
+ }
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_app_layer_protocol_xtn, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = ssl3_AppendHandshakeVariable(ss, alpn_protos, len, 2);
+ PORT_Free(alpn_protos);
+ alpn_protos = NULL;
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_app_layer_protocol_xtn;
+ } else if (maxBytes < extension_length) {
+ return 0;
+ }
+
+ return extension_length;
+
+loser:
+ if (alpn_protos) {
+ PORT_Free(alpn_protos);
+ }
+ return -1;
+}
+
static SECStatus
ssl3_ClientHandleStatusRequestXtn(sslSocket *ss, PRUint16 ex_type,
SECItem *data)
diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h
index 3750c2134..5f70d185b 100644
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -324,6 +324,8 @@ typedef struct sslOptionsStr {
unsigned int enableFalseStart : 1; /* 23 */
unsigned int cbcRandomIV : 1; /* 24 */
unsigned int enableOCSPStapling : 1; /* 25 */
+ unsigned int enableNPN : 1; /* 26 */
+ unsigned int enableALPN : 1; /* 27 */
} sslOptions;
typedef enum { sslHandshakingUndetermined = 0,
diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c
index 60916776d..15344b877 100644
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -78,7 +78,9 @@ static sslOptions ssl_defaults = {
PR_FALSE, /* requireSafeNegotiation */
PR_FALSE, /* enableFalseStart */
PR_TRUE, /* cbcRandomIV */
- PR_FALSE /* enableOCSPStapling */
+ PR_FALSE, /* enableOCSPStapling */
+ PR_TRUE, /* enableNPN */
+ PR_FALSE /* enableALPN */
};
/*
@@ -764,6 +766,14 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRBool on)
ss->opt.enableOCSPStapling = on;
break;
+ case SSL_ENABLE_NPN:
+ ss->opt.enableNPN = on;
+ break;
+
+ case SSL_ENABLE_ALPN:
+ ss->opt.enableALPN = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure;
@@ -834,6 +844,8 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRBool *pOn)
case SSL_ENABLE_FALSE_START: on = ss->opt.enableFalseStart; break;
case SSL_CBC_RANDOM_IV: on = ss->opt.cbcRandomIV; break;
case SSL_ENABLE_OCSP_STAPLING: on = ss->opt.enableOCSPStapling; break;
+ case SSL_ENABLE_NPN: on = ss->opt.enableNPN; break;
+ case SSL_ENABLE_ALPN: on = ss->opt.enableALPN; break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -895,6 +907,8 @@ SSL_OptionGetDefault(PRInt32 which, PRBool *pOn)
case SSL_ENABLE_OCSP_STAPLING:
on = ssl_defaults.enableOCSPStapling;
break;
+ case SSL_ENABLE_NPN: on = ssl_defaults.enableNPN; break;
+ case SSL_ENABLE_ALPN: on = ssl_defaults.enableALPN; break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -1062,6 +1076,14 @@ SSL_OptionSetDefault(PRInt32 which, PRBool on)
ssl_defaults.enableOCSPStapling = on;
break;
+ case SSL_ENABLE_NPN:
+ ssl_defaults.enableNPN = on;
+ break;
+
+ case SSL_ENABLE_ALPN:
+ ssl_defaults.enableALPN = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h
index 350c1bbd7..fb25c6d72 100644
--- a/lib/ssl/sslt.h
+++ b/lib/ssl/sslt.h
@@ -187,12 +187,13 @@ typedef enum {
#endif
ssl_signature_algorithms_xtn = 13,
ssl_use_srtp_xtn = 14,
+ ssl_app_layer_protocol_xtn = 16,
ssl_session_ticket_xtn = 35,
ssl_next_proto_nego_xtn = 13172,
ssl_padding_xtn = 35655,
ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
} SSLExtensionType;
-#define SSL_MAX_EXTENSIONS 9 /* doesn't include ssl_padding_xtn. */
+#define SSL_MAX_EXTENSIONS 10 /* doesn't include ssl_padding_xtn. */
#endif /* __sslt_h_ */