diff options
author | Brian Smith <brian@briansmith.org> | 2014-01-15 21:20:30 -0800 |
---|---|---|
committer | Brian Smith <brian@briansmith.org> | 2014-01-15 21:20:30 -0800 |
commit | 80caf801b2c1fce41362b54af8180a34a7751646 (patch) | |
tree | f0bf7ba8bcd9cd9839e5128c7e24ad9dbe241cea | |
parent | 67e3b1581c554a47f197b5c2bdbccee87336277a (diff) | |
download | nss-hg-80caf801b2c1fce41362b54af8180a34a7751646.tar.gz |
Bug 959664: Add ALPN support to NSS, r=briansmithNSS_3_15_5_BETA2
-rw-r--r-- | lib/ssl/ssl.h | 32 | ||||
-rw-r--r-- | lib/ssl/ssl3con.c | 4 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.c | 123 | ||||
-rw-r--r-- | lib/ssl/sslimpl.h | 2 | ||||
-rw-r--r-- | lib/ssl/sslsock.c | 24 | ||||
-rw-r--r-- | lib/ssl/sslt.h | 3 |
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_ */ |