diff options
author | wtc%google.com <devnull@localhost> | 2007-12-22 00:20:14 +0000 |
---|---|---|
committer | wtc%google.com <devnull@localhost> | 2007-12-22 00:20:14 +0000 |
commit | 89274cc7dee1e331405609324dfd81c1660085bb (patch) | |
tree | 99d112648b5d76c8455518c4eff4106f735e0a74 | |
parent | f942fe90de6faf916c4651e6e2f0b955a1585874 (diff) | |
download | nss-hg-89274cc7dee1e331405609324dfd81c1660085bb.tar.gz |
Bug 403563: implement the TLS session ticket extension as specified in
draft-salowey-tls-rfc4507bis-01.txt. Contributed by Nagendra Modadugu
of Google.
Modified Files:
Tag: NSS_RFC4507BIS_BRANCH
cmd/selfserv/selfserv.c cmd/strsclnt/strsclnt.c
cmd/tstclnt/tstclnt.c lib/ssl/manifest.mn lib/ssl/ssl.h
lib/ssl/ssl3con.c lib/ssl/ssl3ecc.c lib/ssl/ssl3prot.h
lib/ssl/sslerr.h lib/ssl/sslimpl.h lib/ssl/sslnonce.c
lib/ssl/sslsock.c lib/ssl/sslt.h tests/ssl/sslstress.txt
Added Files:
Tag: NSS_RFC4507BIS_BRANCH
lib/ssl/ssl3ext.c
-rw-r--r-- | security/nss/cmd/selfserv/selfserv.c | 35 | ||||
-rw-r--r-- | security/nss/cmd/strsclnt/strsclnt.c | 49 | ||||
-rw-r--r-- | security/nss/cmd/tstclnt/tstclnt.c | 32 | ||||
-rw-r--r-- | security/nss/lib/ssl/manifest.mn | 1 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl.h | 2 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3con.c | 262 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3ecc.c | 207 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3ext.c | 1214 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3prot.h | 48 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslerr.h | 3 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslimpl.h | 120 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslnonce.c | 52 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslsock.c | 17 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslt.h | 5 | ||||
-rw-r--r-- | security/nss/tests/ssl/sslstress.txt | 10 |
15 files changed, 1771 insertions, 286 deletions
diff --git a/security/nss/cmd/selfserv/selfserv.c b/security/nss/cmd/selfserv/selfserv.c index b8a3a2147..d70743c65 100644 --- a/security/nss/cmd/selfserv/selfserv.c +++ b/security/nss/cmd/selfserv/selfserv.c @@ -199,6 +199,7 @@ Usage(const char *progName) " 3 -r's mean request, not require, cert on second handshake.\n" " 4 -r's mean request and require, cert on second handshake.\n" "-s means disable SSL socket locking for performance\n" +"-u means enable Session Ticket extension for TLS1.\n" "-v means verbose output\n" "-x means use export policy.\n" "-L seconds means log statistics every 'seconds' seconds (default=30).\n" @@ -347,19 +348,28 @@ mySSLAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, return rv; } +void +printSSLStatistics() +{ + SSL3Statistics * ssl3stats = SSL_GetStatistics(); + + printf( + "selfserv: %ld cache hits; %ld cache misses, %ld cache not reusable\n" + " %ld stateless resumes\n", + ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, + ssl3stats->hch_sid_cache_not_ok, ssl3stats->hch_sid_stateless_resumes); +} + void printSecurityInfo(PRFileDesc *fd) { CERTCertificate * cert = NULL; - SSL3Statistics * ssl3stats = SSL_GetStatistics(); SECStatus result; SSLChannelInfo channel; SSLCipherSuiteInfo suite; - PRINTF( - "selfserv: %ld cache hits; %ld cache misses, %ld cache not reusable\n", - ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, - ssl3stats->hch_sid_cache_not_ok); + if (verbose) + printSSLStatistics(); result = SSL_GetChannelInfo(fd, &channel, sizeof channel); if (result == SECSuccess && @@ -657,6 +667,7 @@ PRBool disableStepDown = PR_FALSE; PRBool bypassPKCS11 = PR_FALSE; PRBool disableLocking = PR_FALSE; PRBool testbypass = PR_FALSE; +PRBool enableSessionTicketExtension = PR_FALSE; static const char stopCmd[] = { "GET /stop " }; static const char getCmd[] = { "GET " }; @@ -1439,6 +1450,13 @@ server_main( errExit("error disabling SSL socket locking "); } } + if (enableSessionTicketExtension) { + rv = SSL_OptionSet(model_sock, SSL_ENABLE_SESSION_TICKET_EXTENSION, + PR_TRUE); + if (rv != SECSuccess) { + errExit("error enabling Session Ticket extension "); + } + } for (kea = kt_rsa; kea < kt_kea_size; kea++) { if (cert[kea] != NULL) { @@ -1697,7 +1715,7 @@ main(int argc, char **argv) ** numbers, then capital letters, then lower case, alphabetical. */ optstate = PL_CreateOptState(argc, argv, - "2:3BC:DEL:M:NP:RSTbc:d:e:f:hi:lmn:op:qrst:vw:xy"); + "2:3BC:DEL:M:NP:RSTbc:d:e:f:hi:lmn:op:qrst:uvw:xy"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { ++optionsFound; switch(optstate->option) { @@ -1776,6 +1794,8 @@ main(int argc, char **argv) if ( maxThreads < MIN_THREADS ) maxThreads = MIN_THREADS; break; + case 'u': enableSessionTicketExtension = PR_TRUE; break; + case 'v': verbose++; break; case 'w': passwd = PORT_Strdup(optstate->value); break; @@ -2085,6 +2105,8 @@ main(int argc, char **argv) VLOG(("selfserv: server_thread: exiting")); cleanup: + printSSLStatistics(); + { int i; for (i=0; i<kt_kea_size; i++) { @@ -2131,4 +2153,3 @@ cleanup: printf("selfserv: normal termination\n"); return 0; } - diff --git a/security/nss/cmd/strsclnt/strsclnt.c b/security/nss/cmd/strsclnt/strsclnt.c index e56ca4733..540b8bafc 100644 --- a/security/nss/cmd/strsclnt/strsclnt.c +++ b/security/nss/cmd/strsclnt/strsclnt.c @@ -160,6 +160,7 @@ static PRBool disableTLS = PR_FALSE; static PRBool bypassPKCS11 = PR_FALSE; static PRBool disableLocking = PR_FALSE; static PRBool ignoreErrors = PR_FALSE; +static PRBool enableSessionTicketExtension = PR_FALSE; PRIntervalTime maxInterval = PR_INTERVAL_NO_TIMEOUT; @@ -203,7 +204,8 @@ Usage(const char *progName) " -3 means disable SSL3\n" " -T means disable TLS\n" " -U means enable throttling up threads\n" - " -B bypasses the PKCS11 layer for SSL encryption and MACing\n", + " -B bypasses the PKCS11 layer for SSL encryption and MACing\n" + " -u enable TLS Session Ticket extension\n", progName); exit(1); } @@ -347,10 +349,12 @@ printSecurityInfo(PRFileDesc *fd) cert = NULL; } fprintf(stderr, - "strsclnt: %ld cache hits; %ld cache misses, %ld cache not reusable\n", + "strsclnt: %ld cache hits; %ld cache misses, %ld cache not reusable\n" + " %ld stateless resumes\n", ssl3stats->hsh_sid_cache_hits, ssl3stats->hsh_sid_cache_misses, - ssl3stats->hsh_sid_cache_not_ok); + ssl3stats->hsh_sid_cache_not_ok, + ssl3stats->hsh_sid_stateless_resumes); } @@ -1232,6 +1236,13 @@ client_main( } } + if (enableSessionTicketExtension) { + rv = SSL_OptionSet(model_sock, + SSL_ENABLE_SESSION_TICKET_EXTENSION, PR_TRUE); + if (rv != SECSuccess) + errExit("SSL_OptionSet SSL_ENABLE_SESSION_TICKET_EXTENSION"); + } + SSL_SetURL(model_sock, hostName); SSL_AuthCertificateHook(model_sock, mySSLAuthCertificate, @@ -1337,7 +1348,7 @@ main(int argc, char **argv) progName = progName ? progName + 1 : tmp; - optstate = PL_CreateOptState(argc, argv, "23BC:DNP:TUc:d:f:in:op:qst:vw:"); + optstate = PL_CreateOptState(argc, argv, "23BC:DNP:TUc:d:f:in:op:qst:uvw:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch(optstate->option) { @@ -1383,7 +1394,9 @@ main(int argc, char **argv) max_threads = active_threads = tmpInt; break; - case 'v': verbose++; break; + case 'u': enableSessionTicketExtension = PR_TRUE; break; + + case 'v': verbose++; break; case 'w': passwd = PL_strdup(optstate->value); break; @@ -1480,29 +1493,40 @@ main(int argc, char **argv) PL_strfree(hostName); /* some final stats. */ - if (ssl3stats->hsh_sid_cache_hits + ssl3stats->hsh_sid_cache_misses + - ssl3stats->hsh_sid_cache_not_ok == 0) { + if (ssl3stats->hsh_sid_cache_hits + + ssl3stats->hsh_sid_cache_misses + + ssl3stats->hsh_sid_cache_not_ok + + ssl3stats->hsh_sid_stateless_resumes == 0) { /* presumably we were testing SSL2. */ printf("strsclnt: SSL2 - %d server certificates tested.\n", certsTested); } else { printf( - "strsclnt: %ld cache hits; %ld cache misses, %ld cache not reusable\n", + "strsclnt: %ld cache hits; %ld cache misses, %ld cache not reusable\n" + " %ld stateless resumes\n", ssl3stats->hsh_sid_cache_hits, ssl3stats->hsh_sid_cache_misses, - ssl3stats->hsh_sid_cache_not_ok); + ssl3stats->hsh_sid_cache_not_ok, + ssl3stats->hsh_sid_stateless_resumes); } if (!NoReuse) - exitVal = (ssl3stats->hsh_sid_cache_misses > 1) || + exitVal = (enableSessionTicketExtension && + (connections - ssl3stats->hsh_sid_stateless_resumes > 1)) || + (!enableSessionTicketExtension && + ((ssl3stats->hsh_sid_cache_misses > 1) || + (ssl3stats->hsh_sid_stateless_resumes != 0))) || (ssl3stats->hsh_sid_cache_not_ok != 0) || (certsTested > 1); else { printf("strsclnt: NoReuse - %d server certificates tested.\n", certsTested); - if (ssl3stats->hsh_sid_cache_hits + ssl3stats->hsh_sid_cache_misses + - ssl3stats->hsh_sid_cache_not_ok > 0) { + if (ssl3stats->hsh_sid_cache_hits + + ssl3stats->hsh_sid_cache_misses + + ssl3stats->hsh_sid_cache_not_ok + + ssl3stats->hsh_sid_stateless_resumes > 0) { exitVal = (ssl3stats->hsh_sid_cache_misses != connections) || + (ssl3stats->hsh_sid_stateless_resumes != 0) || (certsTested != connections); } else { /* ssl2 connections */ exitVal = (certsTested != connections); @@ -1519,4 +1543,3 @@ main(int argc, char **argv) PR_Cleanup(); return exitVal; } - diff --git a/security/nss/cmd/tstclnt/tstclnt.c b/security/nss/cmd/tstclnt/tstclnt.c index 18fdb503b..5a4172d37 100644 --- a/security/nss/cmd/tstclnt/tstclnt.c +++ b/security/nss/cmd/tstclnt/tstclnt.c @@ -122,6 +122,7 @@ int ssl3CipherSuites[] = { unsigned long __cmp_umuls; PRBool verbose; +int renegotiate = 0; static char *progName; @@ -181,23 +182,26 @@ void printSecurityInfo(PRFileDesc *fd) CERT_DestroyCertificate(cert); cert = NULL; } - fprintf(stderr, - "%ld cache hits; %ld cache misses, %ld cache not reusable\n", - ssl3stats->hsh_sid_cache_hits, ssl3stats->hsh_sid_cache_misses, - ssl3stats->hsh_sid_cache_not_ok); - + fprintf(stderr, "%ld cache hits; %ld stateless resumes; %ld cache misses, " + "%ld cache not reusable\n", ssl3stats->hsh_sid_cache_hits, + ssl3stats->hsh_sid_stateless_resumes, + ssl3stats->hsh_sid_cache_misses, ssl3stats->hsh_sid_cache_not_ok); } void handshakeCallback(PRFileDesc *fd, void *client_data) { printSecurityInfo(fd); + if (renegotiate > 0) { + renegotiate--; + SSL_ReHandshake(fd, PR_FALSE); + } } static void Usage(const char *progName) { fprintf(stderr, -"Usage: %s -h host [-p port] [-d certdir] [-n nickname] [-23BTfosvx] \n" +"Usage: %s -h host [-p port] [-d certdir] [-n nickname] [-23BTfosvxr] \n" " [-c ciphers] [-w passwd] [-q]\n", progName); fprintf(stderr, "%-20s Hostname to connect with\n", "-h host"); fprintf(stderr, "%-20s Port number for SSL server\n", "-p port"); @@ -218,6 +222,8 @@ static void Usage(const char *progName) fprintf(stderr, "%-20s Verbose progress reporting.\n", "-v"); fprintf(stderr, "%-20s Use export policy.\n", "-x"); fprintf(stderr, "%-20s Ping the server and then exit.\n", "-q"); + fprintf(stderr, "%-20s Renegotiate with session resumption.\n", "-r"); + fprintf(stderr, "%-20s Enable the session ticket extension.\n", "-u"); fprintf(stderr, "%-20s Letter(s) chosen from the following list\n", "-c ciphers"); fprintf(stderr, @@ -513,6 +519,7 @@ int main(int argc, char **argv) int bypassPKCS11 = 0; int disableLocking = 0; int useExportPolicy = 0; + int enableSessionTicketExtension = 0; PRSocketOptionData opt; PRNetAddr addr; PRPollDesc pollset[2]; @@ -541,7 +548,7 @@ int main(int argc, char **argv) } } - optstate = PL_CreateOptState(argc, argv, "23BTSfc:h:p:d:m:n:oqsvw:x"); + optstate = PL_CreateOptState(argc, argv, "23BTSfc:h:p:d:m:n:oqr:suvw:x"); while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch (optstate->option) { case '?': @@ -581,8 +588,12 @@ int main(int argc, char **argv) case 's': disableLocking = 1; break; + case 'u': enableSessionTicketExtension = PR_TRUE; break; + case 'v': verbose++; break; + case 'r': renegotiate = atoi(optstate->value); break; + case 'w': password = PORT_Strdup(optstate->value); useCommandLinePassword = PR_TRUE; @@ -825,6 +836,13 @@ int main(int argc, char **argv) return 1; } + /* enable Session Ticket extension. */ + rv = SSL_OptionSet(s, SSL_ENABLE_SESSION_TICKET_EXTENSION, + enableSessionTicketExtension); + if (rv != SECSuccess) { + SECU_PrintError(progName, "error enabling Session Ticket extension"); + return 1; + } if (useCommandLinePassword) { SSL_SetPKCS11PinArg(s, password); diff --git a/security/nss/lib/ssl/manifest.mn b/security/nss/lib/ssl/manifest.mn index 7a63a0f5f..dfcd43031 100644 --- a/security/nss/lib/ssl/manifest.mn +++ b/security/nss/lib/ssl/manifest.mn @@ -64,6 +64,7 @@ CSRCS = \ ssldef.c \ sslenum.c \ sslerr.c \ + ssl3ext.c \ sslgathr.c \ sslmutex.c \ sslnonce.c \ diff --git a/security/nss/lib/ssl/ssl.h b/security/nss/lib/ssl/ssl.h index 56f1691e1..06179409d 100644 --- a/security/nss/lib/ssl/ssl.h +++ b/security/nss/lib/ssl/ssl.h @@ -112,6 +112,8 @@ SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd); /* step-down keys if needed. */ #define SSL_BYPASS_PKCS11 16 /* use PKCS#11 for pub key only */ #define SSL_NO_LOCKS 17 /* Don't use locks for protection */ +#define SSL_ENABLE_SESSION_TICKET_EXTENSION 18 /* Enable TLS1 SessionTicket * + * extension (off by default) */ #ifdef SSL_DEPRECATED_FUNCTION /* Old deprecated function names */ diff --git a/security/nss/lib/ssl/ssl3con.c b/security/nss/lib/ssl/ssl3con.c index 7f981a85a..887dab716 100644 --- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -78,7 +78,6 @@ static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms); static SECStatus ssl3_DeriveConnectionKeysPKCS11(sslSocket *ss); static SECStatus ssl3_HandshakeFailure( sslSocket *ss); static SECStatus ssl3_InitState( sslSocket *ss); -static sslSessionID *ssl3_NewSessionID( sslSocket *ss, PRBool is_server); static SECStatus ssl3_SendCertificate( sslSocket *ss); static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss); static SECStatus ssl3_SendCertificateRequest(sslSocket *ss); @@ -2522,6 +2521,10 @@ ssl3_HandleChangeCipherSpecs(sslSocket *ss, sslBuffer *buf) SSL_TRC(3, ("%d: SSL3[%d]: handle change_cipher_spec record", SSL_GETPID(), ss->fd)); + /* When doing a stateless resume, OpenSSL sends the SessionTicket + * extension but does not send a NewSessionTicket message so we + * work around the bug here. + */ if (ws != wait_change_cipher) { (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message); PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER); @@ -3501,6 +3504,11 @@ ssl3_SendClientHello(sslSocket *ss) return rv; /* ssl3_InitState has set the error code. */ } + /* We might be starting a session renegotiation in which case we should + * clear previous state. + */ + PORT_Memset(&ss->ssl3.extension_data, 0, sizeof(TLS1ExtensionData)); + SSL_TRC(30,("%d: SSL3[%d]: reset handshake hashes", SSL_GETPID(), ss->fd )); rv = ssl3_RestartHandshakeHashes(ss); @@ -3566,6 +3574,11 @@ ssl3_SendClientHello(sslSocket *ss) if (sid) { SSL_AtomicIncrementLong(& ssl3stats.sch_sid_cache_hits ); + /* Are we attempting a stateless session resume? */ + if (sid->version > SSL_LIBRARY_VERSION_3_0 && + sid->u.ssl3.session_ticket.ticket.data) + SSL_AtomicIncrementLong(&ssl3stats.sch_sid_stateless_resumes); + rv = ssl3_NegotiateVersion(ss, sid->version); if (rv != SECSuccess) return rv; /* error code was set */ @@ -4641,8 +4654,15 @@ ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) } ss->ssl3.hs.compression = (SSL3CompressionMethod)temp; -#ifdef DISALLOW_SERVER_HELLO_EXTENSIONS - if (length != 0) { /* malformed */ +#ifndef DISALLOW_SERVER_HELLO_EXTENSIONS + if (isTLS && length > 0) { + SECItem extensions; + rv = ssl3_ConsumeHandshakeVariable(ss, &extensions, 2, &b, &length); + if (rv != SECSuccess || length != 0) + goto alert_loser; + rv = ssl3_HandleHelloExtensions(ss, &extensions.data, &extensions.len); + if (rv != SECSuccess) goto alert_loser; + } else if (length > 0) { goto alert_loser; } #endif @@ -4753,9 +4773,19 @@ ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) /* Got a Match */ SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_cache_hits ); - ss->ssl3.hs.ws = wait_change_cipher; + + /* If we sent a session ticket, then this is a stateless resume. */ + if (sid->version > SSL_LIBRARY_VERSION_3_0 && + sid->u.ssl3.session_ticket.ticket.data != NULL) + SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_stateless_resumes ); + ss->ssl3.hs.isResuming = PR_TRUE; + if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn)) + ss->ssl3.hs.ws = wait_new_session_ticket; + else + ss->ssl3.hs.ws = wait_change_cipher; + /* copy the peer cert from the SID */ if (sid->peerCert != NULL) { ss->sec.peerCert = CERT_DupCertificate(sid->peerCert); @@ -5348,7 +5378,10 @@ ssl3_HandleServerHelloDone(sslSocket *ss) ssl_ReleaseXmitBufLock(ss); /*******************************/ - ss->ssl3.hs.ws = wait_change_cipher; + if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn)) + ss->ssl3.hs.ws = wait_new_session_ticket; + else + ss->ssl3.hs.ws = wait_change_cipher; return SECSuccess; loser: @@ -5389,7 +5422,7 @@ ssl3_SendHelloRequest(sslSocket *ss) * ssl3_HandleClientHello() * ssl3_HandleV2ClientHello() */ -static sslSessionID * +sslSessionID * ssl3_NewSessionID(sslSocket *ss, PRBool is_server) { sslSessionID *sid; @@ -5574,21 +5607,6 @@ ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) goto loser; /* malformed */ } - if (sidBytes.len > 0 && !ss->opt.noCache) { - SSL_TRC(7, ("%d: SSL3[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x", - SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0], - ss->sec.ci.peer.pr_s6_addr32[1], - ss->sec.ci.peer.pr_s6_addr32[2], - ss->sec.ci.peer.pr_s6_addr32[3])); - if (ssl_sid_lookup) { - sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sidBytes.data, - sidBytes.len, ss->dbHandle); - } else { - errCode = SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED; - goto loser; - } - } - /* grab the list of cipher suites. */ rv = ssl3_ConsumeHandshakeVariable(ss, &suites, 2, &b, &length); if (rv != SECSuccess) { @@ -5603,6 +5621,91 @@ ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) desc = handshake_failure; + /* Handle TLS hello extensions, for SSL3 & TLS, We don not know if + * we are restarting a previous session until extensions have been + * parsed, since we might have received a SessionTicket extension. + */ + + /* We might be starting a session renegotiation in which case we should + * clear previous state. + */ + PORT_Memset(&ss->ssl3.extension_data, 0, sizeof(TLS1ExtensionData)); + ss->ssl3.stateless_resume = PR_FALSE; + + /* OpenSSL 0.9.8g sends TLS extensions even when negotiating SSL3, + * so we simply ignore any trailing bytes if the negotiated + * version is not TLS. */ + if (length && ss->version > SSL_LIBRARY_VERSION_3_0) { + /* Get length of hello extensions */ + PRInt32 extension_length; + extension_length = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length); + if (extension_length < 0) { + goto loser; /* alert already sent */ + } + if (extension_length != length) { + ssl3_DecodeError(ss); /* send alert */ + goto loser; + } + rv = ssl3_HandleHelloExtensions(ss, &b, &length); + if (rv != SECSuccess) { + goto loser; /* malformed */ + } + } + + /* We do stateful resumes only if either of the following + * conditions are satisfied: (1) the client does not support the + * session ticket extension, or (2) the client support the session + * ticket extension, but sent an empty ticket. + */ + if (!ssl3_ExtensionNegotiated(ss, session_ticket_xtn) || + ss->ssl3.extension_data.empty_session_ticket) { + if (sidBytes.len > 0 && !ss->opt.noCache) { + SSL_TRC(7, ("%d: SSL3[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x", + SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0], + ss->sec.ci.peer.pr_s6_addr32[1], + ss->sec.ci.peer.pr_s6_addr32[2], + ss->sec.ci.peer.pr_s6_addr32[3])); + if (ssl_sid_lookup) { + sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sidBytes.data, + sidBytes.len, ss->dbHandle); + } else { + errCode = SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED; + goto loser; + } + } + } else if (ss->ssl3.stateless_resume) { + /* Fill in the client's session ID if doing a stateless resume. + * (When doing stateless resumes, server echos client's SessionID.) + */ + sid = ss->sec.ci.sid; + PORT_Assert(sid != NULL); /* Should have already been filled in.*/ + + if (sidBytes.len > 0 && sidBytes.len <= SSL3_SESSIONID_BYTES) { + sid->u.ssl3.sessionIDLength = sidBytes.len; + PORT_Memcpy(sid->u.ssl3.sessionID, sidBytes.data, + sidBytes.len); + sid->u.ssl3.sessionIDLength = sidBytes.len; + } else { + sid->u.ssl3.sessionIDLength = 0; + } + ss->sec.ci.sid = NULL; + } + + /* We only send a session ticket extension if the client supports + * the extension and we are unable to do either a stateful or + * stateless resume. + * + * TODO: send a session ticket if performing a stateful + * resumption. (As per RFC4507, a server may issue a session + * ticket while doing a (stateless or stateful) session resume, + * but OpenSSL-0.9.8g does not accept session tickets while + * resuming.) + */ + if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn) && sid == NULL) { + ssl3_RegisterServerHelloExtensionSender(ss, + session_ticket_xtn, ssl3_SendSessionTicketExt); + } + if (sid != NULL) { /* We've found a session cache entry for this client. * Now, if we're going to require a client-auth cert, @@ -5678,26 +5781,6 @@ ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) /* START A NEW SESSION */ - /* Handle TLS hello extensions, for SSL3 & TLS, - * only if we're not restarting a previous session. - */ - if (length) { - /* Get length of hello extensions */ - PRInt32 extension_length; - extension_length = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length); - if (extension_length < 0) { - goto loser; /* alert already sent */ - } - if (extension_length != length) { - ssl3_DecodeError(ss); /* send alert */ - goto loser; - } - rv = ssl3_HandleClientHelloExtensions(ss, &b, &length); - if (rv != SECSuccess) { - goto loser; /* malformed */ - } - } - #ifndef PARANOID /* Look for a matching cipher suite. */ j = ssl3_config_match_init(ss); @@ -5775,6 +5858,7 @@ compression_found: ssl_GetSpecWriteLock(ss); haveSpecWriteLock = PR_TRUE; pwSpec = ss->ssl3.pwSpec; + if (sid->u.ssl3.keys.msIsWrapped) { PK11SymKey * wrapKey; /* wrapping key */ CK_FLAGS keyFlags = 0; @@ -5842,6 +5926,9 @@ compression_found: * XXX make sure compression still matches */ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_hits ); + if (ss->ssl3.stateless_resume) + SSL_AtomicIncrementLong(&ssl3stats.hch_sid_stateless_resumes); + ss->ssl3.hs.isResuming = PR_TRUE; ss->sec.authAlgorithm = sid->authAlgorithm; @@ -5877,11 +5964,13 @@ compression_found: goto loser; } + rv = ssl3_SendChangeCipherSpecs(ss); if (rv != SECSuccess) { errCode = PORT_GetError(); goto loser; } + rv = ssl3_SendFinished(ss, 0); ss->ssl3.hs.ws = wait_change_cipher; if (rv != SECSuccess) { @@ -6146,12 +6235,12 @@ ssl3_SendServerHello(sslSocket *ss) sid = ss->sec.ci.sid; extensions_len = ssl3_CallHelloExtensionSenders(ss, PR_FALSE, maxBytes, - &ss->serverExtensionSenders[0]); + &ss->ssl3.extension_data.serverExtensionSenders[0]); if (extensions_len > 0) extensions_len += 2; /* Add sizeof total extension length */ length = sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH + 1 + - ((sid == NULL) ? 0: SSL3_SESSIONID_BYTES) + + ((sid == NULL) ? 0: sid->u.ssl3.sessionIDLength) + sizeof(ssl3CipherSuite) + 1 + extensions_len; rv = ssl3_AppendHandshakeHeader(ss, server_hello, length); if (rv != SECSuccess) { @@ -6173,11 +6262,11 @@ ssl3_SendServerHello(sslSocket *ss) return rv; /* err set by AppendHandshake. */ } - if (sid) + if (sid && sid->u.ssl3.sessionIDLength > 0) rv = ssl3_AppendHandshakeVariable( ss, sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength, 1); else - rv = ssl3_AppendHandshakeVariable(ss, NULL, 0, 1); + rv = ssl3_AppendHandshakeNumber(ss, 0, 1); if (rv != SECSuccess) { return rv; /* err set by AppendHandshake. */ } @@ -6198,7 +6287,7 @@ ssl3_SendServerHello(sslSocket *ss) if (rv != SECSuccess) return rv; /* err set by ssl3_SetupPendingCipherSpec */ sent_len = ssl3_CallHelloExtensionSenders(ss, PR_TRUE, extensions_len, - &ss->serverExtensionSenders[0]); + &ss->ssl3.extension_data.serverExtensionSenders[0]); PORT_Assert(sent_len == extensions_len); if (sent_len != extensions_len) { if (sent_len >= 0) @@ -6788,6 +6877,51 @@ ssl3_SendEmptyCertificate(sslSocket *ss) return rv; /* error, if any, set by functions called above. */ } +SECStatus +ssl3_HandleNewSessionTicket(sslSocket *ss, SSL3Opaque *b, PRUint32 length) +{ + SECStatus rv; + NewSessionTicket session_ticket; + + SSL_TRC(3, ("%d: SSL3[%d]: handle session_ticket handshake", + SSL_GETPID(), ss->fd)); + + PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); + + if (ss->ssl3.hs.ws != wait_new_session_ticket) { + SSL3_SendAlert(ss, alert_fatal, unexpected_message); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET); + return SECFailure; + } + + session_ticket.received_timestamp = ssl_Time(); + if (length < 4) { + (void)SSL3_SendAlert(ss, alert_fatal, decode_error); + PORT_SetError(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET); + return SECFailure; + } + session_ticket.ticket_lifetime_hint = + (PRUint32)ssl3_ConsumeHandshakeNumber(ss, 4, &b, &length); + + rv = ssl3_ConsumeHandshakeVariable(ss, &session_ticket.ticket, 2, + &b, &length); + if (length != 0 || rv != SECSuccess) { + (void)SSL3_SendAlert(ss, alert_fatal, decode_error); + PORT_SetError(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET); + return SECFailure; /* malformed */ + } + + rv = ssl3_SetSIDSessionTicket(ss->sec.ci.sid, &session_ticket); + if (rv != SECSuccess) { + (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure); + PORT_SetError(SSL_ERROR_INTERNAL_ERROR_ALERT); + return SECFailure; + } + ss->ssl3.hs.ws = wait_change_cipher; + return SECSuccess; +} + #ifdef NISCC_TEST static PRInt32 connNum = 0; @@ -7374,6 +7508,7 @@ ssl3_ComputeTLSFinished(ssl3CipherSpec *spec, inData.data = (unsigned char *)hashes->md5; inData.len = sizeof hashes[0]; + outData.data = tlsFinished->verify_data; outData.len = sizeof tlsFinished->verify_data; rv = TLS_PRF(&spec->msItem, label, &inData, &outData, isFIPS); @@ -7443,17 +7578,17 @@ fail: /* wrap the master secret, and put it into the SID. * Caller holds the Spec read lock. */ -static SECStatus -ssl3_CacheWrappedMasterSecret(sslSocket *ss, SSL3KEAType effectiveExchKeyType) +SECStatus +ssl3_CacheWrappedMasterSecret(sslSocket *ss, sslSessionID *sid, + ssl3CipherSpec *spec, SSL3KEAType effectiveExchKeyType) { - sslSessionID * sid = ss->sec.ci.sid; PK11SymKey * wrappingKey = NULL; PK11SlotInfo * symKeySlot; void * pwArg = ss->pkcs11PinArg; SECStatus rv = SECFailure; PRBool isServer = ss->sec.isServer; CK_MECHANISM_TYPE mechanism = CKM_INVALID_MECHANISM; - symKeySlot = PK11_GetSlotFromKey(ss->ssl3.crSpec->master_secret); + symKeySlot = PK11_GetSlotFromKey(spec->master_secret); if (!isServer) { int wrapKeyIndex; int incarnation; @@ -7589,6 +7724,20 @@ ssl3_HandleFinished(sslSocket *ss, SSL3Opaque *b, PRUint32 length, (!isServer && ss->ssl3.hs.isResuming)) { PRInt32 flags = 0; + /* Send a NewSessionTicket message if the client sent us + * either an empty session ticket, or one that did not verify. + * (Note that if either of these conditions was met, then the + * server has sent a SessionTicket extension in the + * ServerHello message.) + */ + if (isServer && !ss->ssl3.hs.isResuming && + ssl3_ExtensionNegotiated(ss, session_ticket_xtn)) { + rv = ssl3_SendNewSessionTicket(ss); + if (rv != SECSuccess) { + goto xmit_loser; + } + } + rv = ssl3_SendChangeCipherSpecs(ss); if (rv != SECSuccess) { goto xmit_loser; /* err is set. */ @@ -7663,7 +7812,8 @@ xmit_loser: sid->u.ssl3.keys.msIsWrapped = PR_FALSE; rv = SECSuccess; } else { - rv = ssl3_CacheWrappedMasterSecret(ss, effectiveExchKeyType); + rv = ssl3_CacheWrappedMasterSecret(ss, ss->sec.ci.sid, + ss->ssl3.crSpec, effectiveExchKeyType); sid->u.ssl3.keys.msIsWrapped = PR_TRUE; } ssl_ReleaseSpecReadLock(ss); /*************************************/ @@ -7826,6 +7976,14 @@ ssl3_HandleHandshakeMessage(sslSocket *ss, SSL3Opaque *b, PRUint32 length) } rv = ssl3_HandleClientKeyExchange(ss, b, length); break; + case new_session_ticket: + if (ss->sec.isServer) { + (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message); + PORT_SetError(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET); + return SECFailure; + } + rv = ssl3_HandleNewSessionTicket(ss, b, length); + break; case finished: rv = ssl3_HandleFinished(ss, b, length, &hashes); break; @@ -8255,6 +8413,8 @@ ssl3_InitState(sslSocket *ss) #endif ssl_ReleaseSpecWriteLock(ss); + PORT_Memset(&ss->ssl3.extension_data, 0, sizeof(TLS1ExtensionData)); + rv = ssl3_NewHandshakeHashes(ss); if (rv == SECSuccess) { ss->ssl3.initialized = PR_TRUE; diff --git a/security/nss/lib/ssl/ssl3ecc.c b/security/nss/lib/ssl/ssl3ecc.c index 14c12e28d..970112503 100644 --- a/security/nss/lib/ssl/ssl3ecc.c +++ b/security/nss/lib/ssl/ssl3ecc.c @@ -1048,7 +1048,7 @@ static const PRUint8 ECPtFmt[6] = { * which says that we support all TLS-defined named curves. */ PRInt32 -ssl3_SendSupportedEllipticCurvesExtension( +ssl3_SendSupportedCurvesExt( sslSocket * ss, PRBool append, PRUint32 maxBytes) @@ -1057,6 +1057,14 @@ ssl3_SendSupportedEllipticCurvesExtension( return 0; if (append && maxBytes >= (sizeof EClist)) { SECStatus rv = ssl3_AppendHandshake(ss, EClist, (sizeof EClist)); + if (rv != SECSuccess) + return -1; + if (!ss->sec.isServer) { + TLS1ExtensionData *ex_data = &ss->ssl3.extension_data; + ex_data->advertisedClientExtensions[ + ex_data->numAdvertisedClientExtensions++] = + elliptic_curves_xtn; + } } return (sizeof EClist); } @@ -1065,7 +1073,7 @@ ssl3_SendSupportedEllipticCurvesExtension( * which says that we only support uncompressed points. */ PRInt32 -ssl3_SendSupportedPointFormatsExtension( +ssl3_SendSupportedPointExt( sslSocket * ss, PRBool append, PRUint32 maxBytes) @@ -1074,6 +1082,14 @@ ssl3_SendSupportedPointFormatsExtension( return 0; if (append && maxBytes >= (sizeof ECPtFmt)) { SECStatus rv = ssl3_AppendHandshake(ss, ECPtFmt, (sizeof ECPtFmt)); + if (rv != SECSuccess) + return -1; + if (!ss->sec.isServer) { + TLS1ExtensionData *ex_data = &ss->ssl3.extension_data; + ex_data->advertisedClientExtensions[ + ex_data->numAdvertisedClientExtensions++] = + elliptic_point_formats_xtn; + } } return (sizeof ECPtFmt); } @@ -1081,9 +1097,9 @@ ssl3_SendSupportedPointFormatsExtension( /* Just make sure that the remote client supports uncompressed points, * Since that is all we support. Disable ECC cipher suites if it doesn't. */ -static SECStatus -ssl3_HandleSupportedPointFormatsExtension(sslSocket * ss, PRUint16 ex_type, - SECItem *data) +SECStatus +ssl3_HandleSupportedPointExt(sslSocket * ss, PRUint16 ex_type, + SECItem *data) { int i; @@ -1097,7 +1113,7 @@ ssl3_HandleSupportedPointFormatsExtension(sslSocket * ss, PRUint16 ex_type, /* indicate that we should send a reply */ SECStatus rv; rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type, - &ssl3_SendSupportedPointFormatsExtension); + &ssl3_SendSupportedPointExt); return rv; } } @@ -1128,9 +1144,9 @@ ECName ssl3_GetSvrCertCurveName(sslSocket *ss) /* Ensure that the curve in our server cert is one of the ones suppored * by the remote client, and disable all ECC cipher suites if not. */ -static SECStatus -ssl3_HandleSupportedEllipticCurvesExtension(sslSocket * ss, PRUint16 ex_type, - SECItem *data) +SECStatus +ssl3_HandleSupportedCurvesExt(sslSocket * ss, PRUint16 ex_type, + SECItem *data) { PRInt32 list_len; PRUint32 peerCurves = 0; @@ -1181,176 +1197,3 @@ loser: } #endif /* NSS_ENABLE_ECC */ - -/* Format an SNI extension, using the name from the socket's URL, - * unless that name is a dotted decimal string. - */ -PRInt32 -ssl3_SendServerNameIndicationExtension( - sslSocket * ss, - PRBool append, - PRUint32 maxBytes) -{ - PRUint32 len, span; - /* must have a hostname */ - if (!ss || !ss->url || !ss->url[0]) - return 0; - /* must have at lest one character other than [0-9\.] */ - len = PORT_Strlen(ss->url); - span = strspn(ss->url, "0123456789."); - if (len == span) { - /* is a dotted decimal IP address */ - return 0; - } - if (append && maxBytes >= len + 9) { - SECStatus rv; - /* extension_type */ - rv = ssl3_AppendHandshakeNumber(ss, 0, 2); - if (rv != SECSuccess) return 0; - /* length of extension_data */ - rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2); - if (rv != SECSuccess) return 0; - /* length of server_name_list */ - rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2); - if (rv != SECSuccess) return 0; - /* Name Type (host_name) */ - rv = ssl3_AppendHandshake(ss, "\0", 1); - if (rv != SECSuccess) return 0; - /* HostName (length and value) */ - rv = ssl3_AppendHandshakeVariable(ss, ss->url, len, 2); - if (rv != SECSuccess) return 0; - } - return len + 9; -} - -/* handle an incoming SNI extension, by ignoring it. */ -SECStatus -ssl3_HandleServerNameIndicationExtension(sslSocket * ss, PRUint16 ex_type, - SECItem *data) -{ - /* For now, we ignore this, as if we didn't understand it. :-) */ - return SECSuccess; -} - -/* Table of handlers for received TLS hello extensions, one per extension. - * In the second generation, this table will be dynamic, and functions - * will be registered here. - */ -static const ssl3HelloExtensionHandler handlers[] = { - { 0, &ssl3_HandleServerNameIndicationExtension }, -#ifdef NSS_ENABLE_ECC - { 10, &ssl3_HandleSupportedEllipticCurvesExtension }, - { 11, &ssl3_HandleSupportedPointFormatsExtension }, -#endif - { -1, NULL } -}; - -/* Table of functions to format TLS hello extensions, one per extension. - * This static table is for the formatting of client hello extensions. - * The server's table of hello senders is dynamic, in the socket struct, - * and sender functions are registered there. - */ -static const -ssl3HelloExtensionSender clientHelloSenders[MAX_EXTENSION_SENDERS] = { - { 0, &ssl3_SendServerNameIndicationExtension }, -#ifdef NSS_ENABLE_ECC - { 10, &ssl3_SendSupportedEllipticCurvesExtension }, - { 11, &ssl3_SendSupportedPointFormatsExtension }, -#else - { -1, NULL } -#endif -}; - -/* go through hello extensions in buffer "b". - * For each one, find the extension handler in the table above, and - * if present, invoke that handler. - * ignore any extensions with unknown extension types. - */ -SECStatus -ssl3_HandleClientHelloExtensions(sslSocket *ss, - SSL3Opaque **b, - PRUint32 *length) -{ - while (*length) { - const ssl3HelloExtensionHandler * handler; - SECStatus rv; - PRInt32 extension_type; - SECItem extension_data; - - /* Get the extension's type field */ - extension_type = ssl3_ConsumeHandshakeNumber(ss, 2, b, length); - if (extension_type < 0) /* failure to decode extension_type */ - return SECFailure; /* alert already sent */ - - /* get the data for this extension, so we can pass it or skip it. */ - rv = ssl3_ConsumeHandshakeVariable(ss, &extension_data, 2, b, length); - if (rv != SECSuccess) - return rv; - - /* find extension_type in table of Client Hello Extension Handlers */ - for (handler = handlers; handler->ex_type >= 0; handler++) { - if (handler->ex_type == extension_type) - break; - } - - /* if found, Call this handler */ - if (handler->ex_type == extension_type) { - rv = (*handler->ex_handler)(ss, (PRUint16)extension_type, - &extension_data); - /* Ignore this result */ - /* Essentially, treat all bad extensions as unrecognized types. */ - } - } - return SECSuccess; -} - -/* Add a callback function to the table of senders of server hello extensions. - */ -SECStatus -ssl3_RegisterServerHelloExtensionSender(sslSocket *ss, PRUint16 ex_type, - ssl3HelloExtensionSenderFunc cb) -{ - int i; - ssl3HelloExtensionSender *sender = &ss->serverExtensionSenders[0]; - - for (i = 0; i < MAX_EXTENSION_SENDERS; ++i, ++sender) { - if (!sender->ex_sender) { - sender->ex_type = ex_type; - sender->ex_sender = cb; - return SECSuccess; - } - /* detect duplicate senders */ - PORT_Assert(sender->ex_type != ex_type); - if (sender->ex_type == ex_type) { - /* duplicate */ - break; - } - } - PORT_Assert(i < MAX_EXTENSION_SENDERS); /* table needs to grow */ - PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); - return SECFailure; -} - -/* call each of the extension senders and return the accumulated length */ -PRInt32 -ssl3_CallHelloExtensionSenders(sslSocket *ss, PRBool append, PRUint32 maxBytes, - const ssl3HelloExtensionSender *sender) -{ - PRInt32 total_exten_len = 0; - int i; - - if (!sender) - sender = &clientHelloSenders[0]; - - for (i = 0; i < MAX_EXTENSION_SENDERS; ++i, ++sender) { - if (sender->ex_sender) { - PRInt32 extLen = (*sender->ex_sender)(ss, append, maxBytes); - if (extLen < 0) - return -1; - maxBytes -= extLen; - total_exten_len += extLen; - } - } - return total_exten_len; -} - diff --git a/security/nss/lib/ssl/ssl3ext.c b/security/nss/lib/ssl/ssl3ext.c new file mode 100644 index 000000000..c202c4daa --- /dev/null +++ b/security/nss/lib/ssl/ssl3ext.c @@ -0,0 +1,1214 @@ +/* + * SSL3 Protocol + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dr Vipul Gupta <vipul.gupta@sun.com> and + * Douglas Stebila <douglas@stebila.ca>, Sun Microsystems Laboratories + * Nagendra Modadugu <ngm@google.com>, Google Inc. + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* TLS extension code moved here from ssl3con.c */ +/* $Id$ */ + +#include "nssrenam.h" +#include "nss.h" +#include "ssl.h" +#include "sslimpl.h" +#include "pk11pub.h" +#include "blapi.h" + +static PK11SymKey *session_ticket_enc_key_pkcs11 = NULL; +static PK11SymKey *session_ticket_mac_key_pkcs11 = NULL; +static PRCallOnceType generate_session_keys_once_pkcs11; + +static unsigned char session_ticket_enc_key[32]; +static unsigned char session_ticket_mac_key[SHA256_LENGTH]; +static PRCallOnceType generate_session_keys_once; + +static PRInt32 ssl3_SendServerNameExt(sslSocket * ss, + PRBool append, PRUint32 maxBytes); +static SECStatus ssl3_ParseEncryptedSessionTicket(sslSocket *ss, + SECItem *data, EncryptedSessionTicket *enc_session_ticket); +static SECStatus ssl3_AppendToItem(SECItem *item, const unsigned char *buf, + PRUint32 bytes); +static SECStatus ssl3_AppendNumberToItem(SECItem *item, PRUint32 num, + PRInt32 lenSize); + + +/* + * Write bytes. Using this function means the SECItem structure + * cannot be freed. The caller is expected to call this function + * on a shallow copy of the structure. + */ +static SECStatus +ssl3_AppendToItem(SECItem *item, const unsigned char *buf, PRUint32 bytes) +{ + if (bytes > item->len) + return SECFailure; + + PORT_Memcpy(item->data, buf, bytes); + item->data += bytes; + item->len -= bytes; + return SECSuccess; +} + +/* + * Write a number in network byte order. Using this function means the + * SECItem structure cannot be freed. The caller is expected to call + * this function on a shallow copy of the structure. + */ +static SECStatus +ssl3_AppendNumberToItem(SECItem *item, PRUint32 num, PRInt32 lenSize) +{ + SECStatus rv; + uint8 b[4]; + uint8 * p = b; + + switch (lenSize) { + case 4: + *p++ = (num >> 24) & 0xff; + case 3: + *p++ = (num >> 16) & 0xff; + case 2: + *p++ = (num >> 8) & 0xff; + case 1: + *p = num & 0xff; + } + rv = ssl3_AppendToItem(item, &b[0], lenSize); + return rv; +} + +static SECStatus ssl3_SessionTicketShutdown(void* appData, void* nssData) +{ + if (session_ticket_enc_key_pkcs11) { + PK11_FreeSymKey(session_ticket_enc_key_pkcs11); + session_ticket_enc_key_pkcs11 = NULL; + } + if (session_ticket_mac_key_pkcs11) { + PK11_FreeSymKey(session_ticket_mac_key_pkcs11); + session_ticket_mac_key_pkcs11 = NULL; + } + memset(&generate_session_keys_once_pkcs11, 0, sizeof(PRCallOnceType)); + return SECSuccess; +} + +static PRStatus +ssl3_GenerateSessionTicketKeysPKCS11(void) +{ + PK11SlotInfo *slot; + SECStatus rv; + + slot = PK11_GetBestSlot(CKM_AES_CBC, NULL); + /* no parameter, 128-bit key size */ + session_ticket_enc_key_pkcs11 = + PK11_KeyGen(slot, CKM_AES_KEY_GEN, NULL, 16, NULL); + PK11_FreeSlot(slot); + if (!session_ticket_enc_key_pkcs11) + return PR_FAILURE; + slot = PK11_GetBestSlot(CKM_SHA256_HMAC, NULL); + /* no parameter, 256-bit key size */ + session_ticket_mac_key_pkcs11 = + PK11_KeyGen(slot, CKM_GENERIC_SECRET_KEY_GEN, NULL, 32, NULL); + PK11_FreeSlot(slot); + if (!session_ticket_mac_key_pkcs11) + goto loser; + rv = NSS_RegisterShutdown(ssl3_SessionTicketShutdown, NULL); + if (rv != SECSuccess) + goto loser; + return PR_SUCCESS; + +loser: + ssl3_SessionTicketShutdown(NULL, NULL); + return PR_FAILURE; +} + +SECStatus +ssl3_GetSessionTicketKeysPKCS11(PK11SymKey **aes_key, PK11SymKey **mac_key) +{ + PRStatus rv; + rv = PR_CallOnce(&generate_session_keys_once_pkcs11, + ssl3_GenerateSessionTicketKeysPKCS11); + if (rv != PR_SUCCESS) + return SECFailure; + + *aes_key = session_ticket_enc_key_pkcs11; + *mac_key = session_ticket_mac_key_pkcs11; + + return SECSuccess; +} + +static PRStatus +ssl3_GenerateSessionTicketKeys(void) +{ + SECStatus rv = PK11_GenerateRandom(session_ticket_enc_key, + sizeof(session_ticket_enc_key)); + if (rv != SECSuccess) return PR_FAILURE; + rv = PK11_GenerateRandom(session_ticket_mac_key, + sizeof(session_ticket_mac_key)); + return rv == SECSuccess ? PR_SUCCESS : PR_FAILURE; +} + +SECStatus +ssl3_GetSessionTicketKeys(const unsigned char **aes_key, + PRUint32 *aes_key_length, const unsigned char **mac_key, + PRUint32 *mac_key_length) +{ + PRStatus rv; + rv = PR_CallOnce(&generate_session_keys_once, + ssl3_GenerateSessionTicketKeys); + if (rv != PR_SUCCESS) + return SECFailure; + + *aes_key = session_ticket_enc_key; + *aes_key_length = sizeof(session_ticket_enc_key); + *mac_key = session_ticket_mac_key; + *mac_key_length = sizeof(session_ticket_mac_key); + + return SECSuccess; +} + +/* Table of handlers for received TLS hello extensions, one + * per extension. + * In the second generation, this table will be dynamic, and functions + * will be registered here. + */ +static const ssl3HelloExtensionHandler client_handlers[] = { + { server_name_xtn, &ssl3_HandleServerNameExt }, + { session_ticket_xtn, &ssl3_ClientHandleSessionTicketExt }, + { -1, NULL } +}; + +static const ssl3HelloExtensionHandler server_handlers[] = { + { server_name_xtn, &ssl3_HandleServerNameExt }, +#ifdef NSS_ENABLE_ECC + { elliptic_curves_xtn, &ssl3_HandleSupportedCurvesExt }, + { elliptic_point_formats_xtn, + &ssl3_HandleSupportedPointExt }, +#endif + { session_ticket_xtn, &ssl3_ServerHandleSessionTicketExt }, + { -1, NULL } +}; + +/* Table of functions to format TLS hello extensions, one per extension. + * This static table is for the formatting of client hello extensions. + * The server's table of hello senders is dynamic, in the socket struct, + * and sender functions are registered there. + */ +static const +ssl3HelloExtensionSender clientHelloSenders[MAX_EXTENSION_SENDERS] = { + { server_name_xtn, &ssl3_SendServerNameExt }, +#ifdef NSS_ENABLE_ECC + { elliptic_curves_xtn, &ssl3_SendSupportedCurvesExt }, + { elliptic_point_formats_xtn, + &ssl3_SendSupportedPointExt }, +#endif + { session_ticket_xtn, ssl3_SendSessionTicketExt }, + { -1, NULL } +}; + + +static PRBool +arrayContainsExtension(PRUint16 *array, PRUint32 array_len, PRUint16 ex_type) +{ + int i; + for (i = 0; i < array_len; i++) { + if (ex_type == array[i]) + return PR_TRUE; + } + return PR_FALSE; +} + +PRBool +ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type) { + TLS1ExtensionData *extension_data = &ss->ssl3.extension_data; + return arrayContainsExtension(extension_data->negotiatedExtensions, + extension_data->numNegotiatedExtensions, ex_type); +} + +PRBool +ssl3_ClientExtensionAdvertised(sslSocket *ss, PRUint16 ex_type) { + TLS1ExtensionData *extension_data = &ss->ssl3.extension_data; + return arrayContainsExtension(extension_data->advertisedClientExtensions, + extension_data->numAdvertisedClientExtensions, ex_type); +} + +/* Format an SNI extension, using the name from the socket's URL, + * unless that name is a dotted decimal string. + */ +static PRInt32 +ssl3_SendServerNameExt( + sslSocket * ss, + PRBool append, + PRUint32 maxBytes) +{ + PRUint32 len, span; + /* must have a hostname */ + if (!ss || !ss->url || !ss->url[0]) + return 0; + /* must have at lest one character other than [0-9\.] */ + len = PORT_Strlen(ss->url); + span = strspn(ss->url, "0123456789."); + if (len == span) { + /* is a dotted decimal IP address */ + return 0; + } + if (append && maxBytes >= len + 9) { + SECStatus rv; + /* extension_type */ + rv = ssl3_AppendHandshakeNumber(ss, server_name_xtn, 2); + if (rv != SECSuccess) return -1; + /* length of extension_data */ + rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2); + if (rv != SECSuccess) return -1; + /* length of server_name_list */ + rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2); + if (rv != SECSuccess) return -1; + /* Name Type (host_name) */ + rv = ssl3_AppendHandshake(ss, "\0", 1); + if (rv != SECSuccess) return -1; + /* HostName (length and value) */ + rv = ssl3_AppendHandshakeVariable(ss, (unsigned char *)ss->url, len, 2); + if (rv != SECSuccess) return -1; + if (!ss->sec.isServer) { + TLS1ExtensionData *ex_data = &ss->ssl3.extension_data; + ex_data->advertisedClientExtensions[ + ex_data->numAdvertisedClientExtensions++] = + server_name_xtn; + } + } + return len + 9; +} + +/* handle an incoming SNI extension, by ignoring it. */ +SECStatus +ssl3_HandleServerNameExt(sslSocket * ss, PRUint16 ex_type, + SECItem *data) +{ + /* For now, we ignore this, as if we didn't understand it. :-) */ + return SECSuccess; +} + +/* Called by both clients and servers. + * Clients sends a filled in session ticket if one is available, and otherwise + * sends an empty ticket. Servers always send empty tickets. + */ +PRInt32 +ssl3_SendSessionTicketExt( + sslSocket * ss, + PRBool append, + PRUint32 maxBytes) +{ + sslSessionID *sid; + NewSessionTicket *session_ticket = NULL; + + /* Ignore the SessionTicket extension if processing is disabled. */ + if (!ss->opt.enableSessionTicketExtension) + return 0; + + /* Empty extension length = extension_type (2-bytes) + + * length(extension_data) (2-bytes) + */ + PRInt32 extension_length = 4; + + /* If we are a client then send a session ticket if one is availble. + * Servers that support the extension and are willing to negotiate the + * the extension always respond with an empty extension. + */ + if (!ss->sec.isServer) { + sid = ss->sec.ci.sid; + session_ticket = &sid->u.ssl3.session_ticket; + if (session_ticket->ticket.data) { + if (ss->ssl3.extension_data.ticket_timestamp_verified) { + extension_length += session_ticket->ticket.len; + } else if (!append && + (session_ticket->ticket_lifetime_hint == 0 || + (session_ticket->ticket_lifetime_hint + + session_ticket->received_timestamp > ssl_Time()))) { + extension_length += session_ticket->ticket.len; + ss->ssl3.extension_data.ticket_timestamp_verified = PR_TRUE; + } + } + } + + if (append && maxBytes >= extension_length) { + SECStatus rv; + /* extension_type */ + rv = ssl3_AppendHandshakeNumber(ss, session_ticket_xtn, 2); + if (rv != SECSuccess) + goto loser; + if (session_ticket && session_ticket->ticket.data && + ss->ssl3.extension_data.ticket_timestamp_verified) { + rv = ssl3_AppendHandshakeVariable(ss, session_ticket->ticket.data, + session_ticket->ticket.len, 2); + ss->ssl3.extension_data.ticket_timestamp_verified = PR_FALSE; + } else { + rv = ssl3_AppendHandshakeNumber(ss, 0, 2); + } + if (rv != SECSuccess) + goto loser; + + if (!ss->sec.isServer) { + TLS1ExtensionData *ex_data = &ss->ssl3.extension_data; + ex_data->advertisedClientExtensions[ + ex_data->numAdvertisedClientExtensions++] = + session_ticket_xtn; + } + } else if (maxBytes < extension_length) { + PORT_Assert(0); + return 0; + } + return extension_length; + + loser: + ss->ssl3.extension_data.ticket_timestamp_verified = PR_FALSE; + return -1; +} + +/* + * NewSessionTicket + * Called from ssl3_HandleFinished + */ +SECStatus +ssl3_SendNewSessionTicket(sslSocket *ss) +{ + int i; + SECStatus rv; + NewSessionTicket ticket; + SECItem plaintext; + SECItem plaintext_item = {0, NULL, 0}; + SECItem ciphertext = {0, NULL, 0}; + PRUint32 ciphertext_length; + PRBool ms_is_wrapped; + unsigned char wrapped_ms[SSL3_MASTER_SECRET_LENGTH]; + SECItem ms_item = {0, NULL, 0}; + SSL3KEAType effectiveExchKeyType = ssl_kea_null; + PRUint32 padding_length; + PRUint32 message_length; + PRUint32 cert_length; + uint8 length_buf[4]; + PRUint32 now; + PK11SymKey *aes_key_pkcs11; + PK11SymKey *mac_key_pkcs11; + const unsigned char *aes_key; + const unsigned char *mac_key; + PRUint32 aes_key_length; + PRUint32 mac_key_length; + PRUint64 aes_ctx_buf[MAX_CIPHER_CONTEXT_LLONGS]; + AESContext *aes_ctx; + CK_MECHANISM_TYPE cipherMech = CKM_AES_CBC; + PK11Context *aes_ctx_pkcs11; + const SECHashObject *hashObj = NULL; + PRUint64 hmac_ctx_buf[MAX_MAC_CONTEXT_LLONGS]; + HMACContext *hmac_ctx; + CK_MECHANISM_TYPE macMech = CKM_SHA256_HMAC; + PK11Context *hmac_ctx_pkcs11; + unsigned char computed_mac[TLS1_EX_SESS_TICKET_MAC_LENGTH]; + unsigned int computed_mac_length; + unsigned char iv[AES_BLOCK_SIZE]; + SECItem ivItem; + CK_MECHANISM_TYPE msWrapMech = 0; /* dummy default value, + * must be >= 0 */ + + SSL_TRC(3, ("%d: SSL3[%d]: send session_ticket handshake", + SSL_GETPID(), ss->fd)); + + PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss)); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); + + ticket.ticket_lifetime_hint = TLS1_EX_SESS_TICKET_LIFETIME_HINT; + cert_length = (ss->opt.requestCertificate && ss->sec.ci.sid->peerCert) ? + 3 + ss->sec.ci.sid->peerCert->derCert.len : 0; + + /* Get IV and encryption keys */ + ivItem.data = iv; + ivItem.len = sizeof(iv); + rv = PK11_GenerateRandom(iv, sizeof(iv)); + if (rv != SECSuccess) goto loser; + + if (ss->opt.bypassPKCS11) { + rv = ssl3_GetSessionTicketKeys(&aes_key, &aes_key_length, + &mac_key, &mac_key_length); + } else { + rv = ssl3_GetSessionTicketKeysPKCS11(&aes_key_pkcs11, + &mac_key_pkcs11); + } + if (rv != SECSuccess) goto loser; + + if (ss->ssl3.pwSpec->msItem.len && ss->ssl3.pwSpec->msItem.data) { + /* The master secret is available unwrapped. */ + ms_item.data = ss->ssl3.pwSpec->msItem.data; + ms_item.len = ss->ssl3.pwSpec->msItem.len; + ms_is_wrapped = PR_FALSE; + } else { + /* Extract the master secret wrapped. */ + sslSessionID sid; + PORT_Memset(&sid, 0, sizeof(sslSessionID)); + + if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) { + effectiveExchKeyType = kt_rsa; + } else { + effectiveExchKeyType = ss->ssl3.hs.kea_def->exchKeyType; + } + + rv = ssl3_CacheWrappedMasterSecret(ss, &sid, ss->ssl3.pwSpec, + effectiveExchKeyType); + if (rv == SECSuccess) { + if (sid.u.ssl3.keys.wrapped_master_secret_len > sizeof(wrapped_ms)) + goto loser; + memcpy(wrapped_ms, sid.u.ssl3.keys.wrapped_master_secret, + sid.u.ssl3.keys.wrapped_master_secret_len); + ms_item.data = wrapped_ms; + ms_item.len = sid.u.ssl3.keys.wrapped_master_secret_len; + msWrapMech = sid.u.ssl3.masterWrapMech; + } else { + /* TODO: else send an empty ticket. */ + goto loser; + } + ms_is_wrapped = PR_TRUE; + } + + ciphertext_length = + sizeof(PRUint16) /* ticket_version */ + + sizeof(SSL3ProtocolVersion) /* ssl_version */ + + sizeof(ssl3CipherSuite) /* ciphersuite */ + + 1 /* compression */ + + 10 /* cipher spec parameters */ + + 1 /* SessionTicket.ms_is_wrapped */ + + 1 /* effectiveExchKeyType */ + + 4 /* msWrapMech */ + + 2 /* master_secret.length */ + + ms_item.len /* master_secret */ + + 1 /* client_auth_type */ + + cert_length /* cert */ + + sizeof(ticket.ticket_lifetime_hint); + padding_length = AES_BLOCK_SIZE - + (ciphertext_length % AES_BLOCK_SIZE); + ciphertext_length += padding_length; + + message_length = + sizeof(ticket.ticket_lifetime_hint) /* ticket_lifetime_hint */ + + 2 /* length field for NewSessionTicket.ticket */ + + sizeof(TLS1_EX_SESS_TICKET_KEY_NAME) /* key_name */ + + AES_BLOCK_SIZE /* iv */ + + 2 /* length field for NewSessionTicket.ticket.encrypted_state */ + + ciphertext_length /* encrypted_state */ + + TLS1_EX_SESS_TICKET_MAC_LENGTH; /* mac */ + + if (SECITEM_AllocItem(NULL, &plaintext_item, ciphertext_length) == NULL) + goto loser; + + plaintext = plaintext_item; + + /* ticket_version */ + rv = ssl3_AppendNumberToItem(&plaintext, TLS1_EX_SESS_TICKET_VERSION, + sizeof(PRUint16)); + if (rv != SECSuccess) goto loser; + + /* ssl_version */ + rv = ssl3_AppendNumberToItem(&plaintext, ss->version, + sizeof(SSL3ProtocolVersion)); + if (rv != SECSuccess) goto loser; + + /* ciphersuite */ + rv = ssl3_AppendNumberToItem(&plaintext, ss->ssl3.hs.cipher_suite, + sizeof(ssl3CipherSuite)); + if (rv != SECSuccess) goto loser; + + /* compression */ + rv = ssl3_AppendNumberToItem(&plaintext, ss->ssl3.hs.compression, 1); + if (rv != SECSuccess) goto loser; + + /* cipher spec parameters */ + rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.authAlgorithm, 1); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.authKeyBits, 4); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.keaType, 1); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.keaKeyBits, 4); + if (rv != SECSuccess) goto loser; + + /* master_secret */ + rv = ssl3_AppendNumberToItem(&plaintext, ms_is_wrapped, 1); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, effectiveExchKeyType, 1); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, msWrapMech, 4); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, ms_item.len, 2); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendToItem(&plaintext, ms_item.data, ms_item.len); + if (rv != SECSuccess) goto loser; + + /* client_identity */ + if (ss->opt.requestCertificate && ss->sec.ci.sid->peerCert) { + rv = ssl3_AppendNumberToItem(&plaintext, CLIENT_AUTH_CERTIFICATE, 1); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendNumberToItem(&plaintext, + ss->sec.ci.sid->peerCert->derCert.len, 3); + if (rv != SECSuccess) goto loser; + rv = ssl3_AppendToItem(&plaintext, + ss->sec.ci.sid->peerCert->derCert.data, + ss->sec.ci.sid->peerCert->derCert.len); + if (rv != SECSuccess) goto loser; + } else { + rv = ssl3_AppendNumberToItem(&plaintext, 0, 1); + if (rv != SECSuccess) goto loser; + } + + /* timestamp */ + now = ssl_Time(); + rv = ssl3_AppendNumberToItem(&plaintext, now, + sizeof(ticket.ticket_lifetime_hint)); + if (rv != SECSuccess) goto loser; + + PORT_Assert(plaintext.len == padding_length); + for (i = 0; i < padding_length; i++) + plaintext.data[i] = (unsigned char)padding_length; + + if (SECITEM_AllocItem(NULL, &ciphertext, ciphertext_length) == NULL) { + rv = SECFailure; + goto loser; + } + + /* Generate encrypted portion of ticket. */ + if (ss->opt.bypassPKCS11) { + aes_ctx = (AESContext *)aes_ctx_buf; + rv = AES_InitContext(aes_ctx, aes_key, aes_key_length, iv, + NSS_AES_CBC, 1, AES_BLOCK_SIZE); + if (rv != SECSuccess) goto loser; + + rv = AES_Encrypt(aes_ctx, ciphertext.data, &ciphertext.len, + ciphertext.len, plaintext_item.data, + plaintext_item.len); + if (rv != SECSuccess) goto loser; + } else { + aes_ctx_pkcs11 = PK11_CreateContextBySymKey(cipherMech, + CKA_ENCRYPT, aes_key_pkcs11, &ivItem); + if (!aes_ctx_pkcs11) + goto loser; + + rv = PK11_CipherOp(aes_ctx_pkcs11, ciphertext.data, &ciphertext.len, + ciphertext.len, plaintext_item.data, plaintext_item.len); + PK11_Finalize(aes_ctx_pkcs11); + PK11_DestroyContext(aes_ctx_pkcs11, PR_TRUE); + if (rv != SECSuccess) goto loser; + } + + /* Convert ciphertext length to network order. */ + length_buf[0] = (ciphertext.len >> 8) & 0xff; + length_buf[1] = (ciphertext.len ) & 0xff; + + /* Compute MAC. */ + if (ss->opt.bypassPKCS11) { + hmac_ctx = (HMACContext *)hmac_ctx_buf; + hashObj = HASH_GetRawHashObject(HASH_AlgSHA256); + if (HMAC_Init(hmac_ctx, hashObj, mac_key, + mac_key_length, PR_FALSE) != SECSuccess) + goto loser; + + HMAC_Begin(hmac_ctx); + HMAC_Update(hmac_ctx, (unsigned char *)TLS1_EX_SESS_TICKET_KEY_NAME, + sizeof(TLS1_EX_SESS_TICKET_KEY_NAME)); + HMAC_Update(hmac_ctx, iv, sizeof(iv)); + HMAC_Update(hmac_ctx, (unsigned char *)length_buf, 2); + HMAC_Update(hmac_ctx, ciphertext.data, ciphertext.len); + HMAC_Finish(hmac_ctx, computed_mac, &computed_mac_length, + sizeof(computed_mac)); + } else { + SECItem macParam; + macParam.data = NULL; + macParam.len = 0; + hmac_ctx_pkcs11 = PK11_CreateContextBySymKey(macMech, + CKA_SIGN, mac_key_pkcs11, &macParam); + if (!hmac_ctx_pkcs11) + goto loser; + + rv = PK11_DigestBegin(hmac_ctx_pkcs11); + rv = PK11_DigestOp(hmac_ctx_pkcs11, + (unsigned char *)TLS1_EX_SESS_TICKET_KEY_NAME, + sizeof(TLS1_EX_SESS_TICKET_KEY_NAME)); + rv = PK11_DigestOp(hmac_ctx_pkcs11, iv, sizeof(iv)); + rv = PK11_DigestOp(hmac_ctx_pkcs11, (unsigned char *)length_buf, 2); + rv = PK11_DigestOp(hmac_ctx_pkcs11, ciphertext.data, ciphertext.len); + rv = PK11_DigestFinal(hmac_ctx_pkcs11, computed_mac, + &computed_mac_length, sizeof(computed_mac)); + PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE); + if (rv != SECSuccess) goto loser; + } + + /* Serialize the handshake message. */ + rv = ssl3_AppendHandshakeHeader(ss, new_session_ticket, message_length); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshakeNumber(ss, ticket.ticket_lifetime_hint, + sizeof(ticket.ticket_lifetime_hint)); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshakeNumber(ss, + message_length - sizeof(ticket.ticket_lifetime_hint) - 2, 2); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshake(ss, + (unsigned char *)TLS1_EX_SESS_TICKET_KEY_NAME, + sizeof(TLS1_EX_SESS_TICKET_KEY_NAME)); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshake(ss, iv, sizeof(iv)); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshakeVariable(ss, ciphertext.data, ciphertext.len, 2); + if (rv != SECSuccess) goto loser; + + rv = ssl3_AppendHandshake(ss, computed_mac, computed_mac_length); + if (rv != SECSuccess) goto loser; + +loser: + if (plaintext_item.data) + SECITEM_FreeItem(&plaintext_item, PR_FALSE); + if (ciphertext.data) + SECITEM_FreeItem(&ciphertext, PR_FALSE); + + return rv; +} + +/* When a client receives a SessionTicket extension a NewSessionTicket + * message is expected during the handshake. + */ +SECStatus +ssl3_ClientHandleSessionTicketExt(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + TLS1ExtensionData *ex_data; + if (data->len != 0) + return SECFailure; + + /* Keep track of negotiated extensions. */ + ex_data = &ss->ssl3.extension_data; + ex_data->negotiatedExtensions[ex_data->numNegotiatedExtensions++] = + ex_type; + return SECSuccess; +} + +SECStatus +ssl3_ServerHandleSessionTicketExt(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + SECStatus rv; + SECItem *decrypted_state = NULL; + SessionTicket *parsed_session_ticket = NULL; + sslSessionID *sid = NULL; + + + /* Ignore the SessionTicket extension if processing is disabled. */ + if (!ss->opt.enableSessionTicketExtension) + return SECSuccess; + + /* Keep track of negotiated extensions. */ + ss->ssl3.extension_data.negotiatedExtensions[ + ss->ssl3.extension_data.numNegotiatedExtensions++] = ex_type; + + /* Parse the received ticket sent in by the client. We are + * lenient about some parse errors, falling back to a fullshake + * instead of terminating the current connection. + */ + if (data->len == 0) { + ss->ssl3.extension_data.empty_session_ticket = PR_TRUE; + } else { + int i; + SECItem extension_data; + EncryptedSessionTicket enc_session_ticket; + unsigned char computed_mac[TLS1_EX_SESS_TICKET_MAC_LENGTH]; + unsigned int computed_mac_length; + const SECHashObject *hashObj; + PRUint64 hmac_ctx_buf[MAX_MAC_CONTEXT_LLONGS]; + HMACContext *hmac_ctx; + PK11Context *hmac_ctx_pkcs11; + CK_MECHANISM_TYPE macMech = CKM_SHA256_HMAC; + PRUint64 aes_ctx_buf[MAX_CIPHER_CONTEXT_LLONGS]; + AESContext *aes_ctx; + PK11Context *aes_ctx_pkcs11; + CK_MECHANISM_TYPE cipherMech = CKM_AES_CBC; + unsigned char * padding; + PRUint32 padding_length; + unsigned char *buffer; + unsigned int buffer_len; + PRInt32 temp; + SECItem cert_item; + + /* Turn off stateless session resumption if the client sends a + * SessionTicket extension, even if the extension turns out to be + * malformed (ss->sec.ci.sid is non-NULL when doing session + * renegotiation.) + */ + if (ss->sec.ci.sid != NULL) { + ss->sec.uncache(ss->sec.ci.sid); + ssl_FreeSID(ss->sec.ci.sid); + ss->sec.ci.sid = NULL; + } + + extension_data.data = data->data; /* Keep a copy for future use. */ + extension_data.len = data->len; + + if (ssl3_ParseEncryptedSessionTicket(ss, data, &enc_session_ticket) + != SECSuccess) + return SECFailure; + + /* Verify the MAC on the ticket. MAC verification may also + * fail if the MAC key has been recently refreshed. + */ + if (ss->opt.bypassPKCS11) { + hmac_ctx = (HMACContext *)hmac_ctx_buf; + hashObj = HASH_GetRawHashObject(HASH_AlgSHA256); + if (HMAC_Init(hmac_ctx, hashObj, session_ticket_mac_key, + sizeof(session_ticket_mac_key), PR_FALSE) != SECSuccess) + goto no_ticket; + HMAC_Begin(hmac_ctx); + HMAC_Update(hmac_ctx, extension_data.data, + extension_data.len - TLS1_EX_SESS_TICKET_MAC_LENGTH); + if (HMAC_Finish(hmac_ctx, computed_mac, &computed_mac_length, + sizeof(computed_mac)) != SECSuccess) + goto no_ticket; + } else { + SECItem macParam; + macParam.data = NULL; + macParam.len = 0; + hmac_ctx_pkcs11 = PK11_CreateContextBySymKey(macMech, + CKA_SIGN, session_ticket_mac_key_pkcs11, &macParam); + if (!hmac_ctx_pkcs11) + goto no_ticket; + rv = PK11_DigestBegin(hmac_ctx_pkcs11); + rv = PK11_DigestOp(hmac_ctx_pkcs11, extension_data.data, + extension_data.len - TLS1_EX_SESS_TICKET_MAC_LENGTH); + if (rv != SECSuccess) { + PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE); + goto no_ticket; + } + rv = PK11_DigestFinal(hmac_ctx_pkcs11, computed_mac, + &computed_mac_length, sizeof(computed_mac)); + PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE); + if (rv != SECSuccess) + goto no_ticket; + } + if (PORT_Memcmp(computed_mac, enc_session_ticket.mac, + computed_mac_length) != 0) + goto no_ticket; + + /* We ignore key_name for now. + * This is ok as MAC verification succeeded. + */ + + /* Decrypt the ticket. */ + + /* Plaintext is shorter than the ciphertext due to padding. */ + decrypted_state = SECITEM_AllocItem(NULL, NULL, + enc_session_ticket.encrypted_state.len); + + if (ss->opt.bypassPKCS11) { + aes_ctx = (AESContext *)aes_ctx_buf; + rv = AES_InitContext(aes_ctx, session_ticket_enc_key, + sizeof(session_ticket_enc_key), enc_session_ticket.iv, + NSS_AES_CBC, 0,AES_BLOCK_SIZE); + if (rv != SECSuccess) + goto no_ticket; + + rv = AES_Decrypt(aes_ctx, decrypted_state->data, + &decrypted_state->len, decrypted_state->len, + enc_session_ticket.encrypted_state.data, + enc_session_ticket.encrypted_state.len); + if (rv != SECSuccess) + goto no_ticket; + } else { + SECItem ivItem; + ivItem.data = enc_session_ticket.iv; + ivItem.len = AES_BLOCK_SIZE; + aes_ctx_pkcs11 = PK11_CreateContextBySymKey(cipherMech, + CKA_DECRYPT, session_ticket_enc_key_pkcs11, &ivItem); + if (!aes_ctx_pkcs11) + goto no_ticket; + + rv = PK11_CipherOp(aes_ctx_pkcs11, decrypted_state->data, + &decrypted_state->len, decrypted_state->len, + enc_session_ticket.encrypted_state.data, + enc_session_ticket.encrypted_state.len); + PK11_Finalize(aes_ctx_pkcs11); + PK11_DestroyContext(aes_ctx_pkcs11, PR_TRUE); + if (rv != SECSuccess) + goto no_ticket; + } + + /* Check padding. */ + padding_length = + (PRUint32)decrypted_state->data[decrypted_state->len - 1]; + if (padding_length == 0 || padding_length > AES_BLOCK_SIZE) + goto no_ticket; + + padding = &decrypted_state->data[decrypted_state->len - padding_length]; + for (i = 0; i < padding_length; i++, padding++) { + if (padding_length != (PRUint32)*padding) + goto no_ticket; + } + + /* Deserialize session state. */ + buffer = decrypted_state->data; + buffer_len = decrypted_state->len; + + parsed_session_ticket = PORT_ZAlloc(sizeof(SessionTicket)); + if (parsed_session_ticket == NULL) { + rv = SECFailure; + goto loser; + } + + /* Read ticket_version (which is ignored for now.) */ + temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->ticket_version = (SSL3ProtocolVersion)temp; + + /* Read SSLVersion. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->ssl_version = (SSL3ProtocolVersion)temp; + + /* Read cipher_suite. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->cipher_suite = (ssl3CipherSuite)temp; + + /* Read compression_method. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->compression_method = (SSL3CompressionMethod)temp; + + /* Read cipher spec parameters. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->authAlgorithm = (SSLSignType)temp; + temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->authKeyBits = (PRUint32)temp; + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->keaType = (SSLKEAType)temp; + temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->keaKeyBits = (PRUint32)temp; + + /* Read wrapped master_secret. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->ms_is_wrapped = (PRBool)temp; + + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->exchKeyType = (SSL3KEAType)temp; + + temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->msWrapMech = (CK_MECHANISM_TYPE)temp; + + temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len); + if (temp < 0) goto no_ticket; + parsed_session_ticket->ms_length = (PRUint16)temp; + if (parsed_session_ticket->ms_length == 0 || /* sanity check MS. */ + parsed_session_ticket->ms_length > + sizeof(parsed_session_ticket->master_secret)) + goto no_ticket; + + /* Allow for the wrapped master secret to be longer. */ + if (buffer_len < sizeof(SSL3_MASTER_SECRET_LENGTH)) + goto no_ticket; + PORT_Memcpy(parsed_session_ticket->master_secret, buffer, + parsed_session_ticket->ms_length); + buffer += parsed_session_ticket->ms_length; + buffer_len -= parsed_session_ticket->ms_length; + + /* Read client_identity */ + temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len); + if (temp < 0) + goto no_ticket; + parsed_session_ticket->client_identity.client_auth_type = + (ClientAuthenticationType)temp; + switch(parsed_session_ticket->client_identity.client_auth_type) { + case CLIENT_AUTH_ANONYMOUS: + break; + case CLIENT_AUTH_CERTIFICATE: + rv = ssl3_ConsumeHandshakeVariable(ss, &cert_item, 3, + &buffer, &buffer_len); + if (rv != SECSuccess) goto no_ticket; + rv = SECITEM_CopyItem(NULL, &parsed_session_ticket->peer_cert, + &cert_item); + if (rv != SECSuccess) goto no_ticket; + break; + default: + goto no_ticket; + } + /* Read timestamp. */ + temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len); + if (temp < 0) + goto no_ticket; + parsed_session_ticket->timestamp = (PRUint32)temp; + + /* Done parsing. Check that all bytes have been consumed. */ + if (buffer_len != padding_length) + goto no_ticket; + + /* Use the ticket if it has not expired, otherwise free the allocated + * memory since the ticket is of no use. + */ + if (parsed_session_ticket->timestamp != 0 && + parsed_session_ticket->timestamp + TLS1_EX_SESS_TICKET_LIFETIME_HINT > ssl_Time()) { + + sid = ssl3_NewSessionID(ss, PR_TRUE); + if (sid == NULL) { + rv = SECFailure; + goto loser; + } + + /* Copy over parameters. */ + sid->version = parsed_session_ticket->ssl_version; + sid->u.ssl3.cipherSuite = parsed_session_ticket->cipher_suite; + sid->u.ssl3.compression = parsed_session_ticket->compression_method; + sid->authAlgorithm = parsed_session_ticket->authAlgorithm; + sid->authKeyBits = parsed_session_ticket->authKeyBits; + sid->keaType = parsed_session_ticket->keaType; + sid->keaKeyBits = parsed_session_ticket->keaKeyBits; + + /* Copy master secret. */ + if (ss->opt.bypassPKCS11 && + parsed_session_ticket->ms_is_wrapped) + goto no_ticket; + if (parsed_session_ticket->ms_length > + sizeof(sid->u.ssl3.keys.wrapped_master_secret)) + goto no_ticket; + PORT_Memcpy(sid->u.ssl3.keys.wrapped_master_secret, + parsed_session_ticket->master_secret, + parsed_session_ticket->ms_length); + sid->u.ssl3.keys.wrapped_master_secret_len = + parsed_session_ticket->ms_length; + sid->u.ssl3.exchKeyType = parsed_session_ticket->exchKeyType; + sid->u.ssl3.masterWrapMech = parsed_session_ticket->msWrapMech; + sid->u.ssl3.keys.msIsWrapped = + parsed_session_ticket->ms_is_wrapped; + sid->u.ssl3.masterValid = PR_TRUE; + sid->u.ssl3.keys.resumable = PR_TRUE; + + /* Copy over client cert from session ticket if there is one. */ + if (parsed_session_ticket->peer_cert.data != NULL) { + if (sid->peerCert != NULL) + CERT_DestroyCertificate(sid->peerCert); + sid->peerCert = CERT_NewTempCertificate(ss->dbHandle, + &parsed_session_ticket->peer_cert, NULL, PR_FALSE, PR_TRUE); + if (sid->peerCert == NULL) { + rv = SECFailure; + goto loser; + } + } + ss->ssl3.stateless_resume = PR_TRUE; + ss->sec.ci.sid = sid; + } + } + + if (0) { +no_ticket: + if (sid) { + ssl_FreeSID(sid); + sid = NULL; + } + } + rv = SECSuccess; + +loser: + if (decrypted_state != NULL) { + SECITEM_FreeItem(decrypted_state, PR_TRUE); + decrypted_state = NULL; + } + + if (parsed_session_ticket != NULL) { + if (parsed_session_ticket->peer_cert.data) { + SECITEM_FreeItem(&parsed_session_ticket->peer_cert, PR_FALSE); + } + PORT_ZFree(parsed_session_ticket, sizeof(SessionTicket)); + } + + return rv; +} + +/* + * Read bytes. Using this function means the SECItem structure + * cannot be freed. The caller is expected to call this function + * on a shallow copy of the structure. + */ +static SECStatus +ssl3_ConsumeFromItem(SECItem *item, unsigned char **buf, PRUint32 bytes) +{ + if (bytes > item->len) + return SECFailure; + + *buf = item->data; + item->data += bytes; + item->len -= bytes; + return SECSuccess; +} + +static SECStatus +ssl3_ParseEncryptedSessionTicket(sslSocket *ss, SECItem *data, + EncryptedSessionTicket *enc_session_ticket) +{ + if (ssl3_ConsumeFromItem(data, &enc_session_ticket->key_name, + sizeof(TLS1_EX_SESS_TICKET_KEY_NAME)) != SECSuccess) + return SECFailure; + if (ssl3_ConsumeFromItem(data, &enc_session_ticket->iv, + AES_BLOCK_SIZE) != SECSuccess) + return SECFailure; + if (ssl3_ConsumeHandshakeVariable(ss, &enc_session_ticket->encrypted_state, + 2, &data->data, &data->len) != SECSuccess) + return SECFailure; + if (ssl3_ConsumeFromItem(data, &enc_session_ticket->mac, + TLS1_EX_SESS_TICKET_MAC_LENGTH) != SECSuccess) + return SECFailure; + if (data->len != 0) /* Make sure that we have consumed all bytes. */ + return SECFailure; + + return SECSuccess; +} + +/* go through hello extensions in buffer "b". + * For each one, find the extension handler in the table above, and + * if present, invoke that handler. + * ignore any extensions with unknown extension types. + */ +SECStatus +ssl3_HandleHelloExtensions(sslSocket *ss, + SSL3Opaque **b, + PRUint32 *length) +{ + const ssl3HelloExtensionHandler * handlers = + ss->sec.isServer ? server_handlers : client_handlers; + + while (*length) { + SECStatus rv; + PRInt32 extension_type; + SECItem extension_data; + TLS1ExtensionData *ex_data = &ss->ssl3.extension_data; + const ssl3HelloExtensionHandler * handler; + + /* Get the extension's type field */ + extension_type = ssl3_ConsumeHandshakeNumber(ss, 2, b, length); + if (extension_type < 0) /* failure to decode extension_type */ + return SECFailure; /* alert already sent */ + + /* get the data for this extension, so we can pass it or skip it. */ + rv = ssl3_ConsumeHandshakeVariable(ss, &extension_data, 2, b, length); + if (rv != SECSuccess) + return rv; + + /* Check whether the server sent an extension which was not advertised + * in the ClientHello. + */ + if (!ss->sec.isServer && + !ssl3_ClientExtensionAdvertised(ss, extension_type)) + return SECFailure; + + /* Check whether an extension has been sent multiple times. */ + if (arrayContainsExtension(ex_data->negotiatedExtensions, + ex_data->numNegotiatedExtensions, extension_type)) + return SECFailure; + + /* find extension_type in table of Client Hello Extension Handlers */ + for (handler = handlers; handler->ex_type >= 0; handler++) { + /* if found, Call this handler */ + if (handler->ex_type == extension_type) { + rv = (*handler->ex_handler)(ss, (PRUint16)extension_type, + &extension_data); + /* Ignore this result */ + /* Treat all bad extensions as unrecognized types. */ + break; + } + } + } + return SECSuccess; +} + +/* Add a callback function to the table of senders of server hello extensions. + */ +SECStatus +ssl3_RegisterServerHelloExtensionSender(sslSocket *ss, PRUint16 ex_type, + ssl3HelloExtensionSenderFunc cb) +{ + int i; + ssl3HelloExtensionSender *sender = + &ss->ssl3.extension_data.serverExtensionSenders[0]; + + for (i = 0; i < MAX_EXTENSION_SENDERS; ++i, ++sender) { + if (!sender->ex_sender) { + sender->ex_type = ex_type; + sender->ex_sender = cb; + return SECSuccess; + } + /* detect duplicate senders */ + PORT_Assert(sender->ex_type != ex_type); + if (sender->ex_type == ex_type) { + /* duplicate */ + break; + } + } + PORT_Assert(i < MAX_EXTENSION_SENDERS); /* table needs to grow */ + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; +} + +/* call each of the extension senders and return the accumulated length */ +PRInt32 +ssl3_CallHelloExtensionSenders(sslSocket *ss, PRBool append, PRUint32 maxBytes, + const ssl3HelloExtensionSender *sender) +{ + PRInt32 total_exten_len = 0; + int i; + + if (!sender) + sender = &clientHelloSenders[0]; + + for (i = 0; i < MAX_EXTENSION_SENDERS; ++i, ++sender) { + if (sender->ex_sender) { + PRInt32 extLen = (*sender->ex_sender)(ss, append, maxBytes); + if (extLen < 0) + return -1; + maxBytes -= extLen; + total_exten_len += extLen; + } + } + return total_exten_len; +} diff --git a/security/nss/lib/ssl/ssl3prot.h b/security/nss/lib/ssl/ssl3prot.h index b57314005..83ab67e7d 100644 --- a/security/nss/lib/ssl/ssl3prot.h +++ b/security/nss/lib/ssl/ssl3prot.h @@ -150,13 +150,14 @@ typedef enum { hello_request = 0, client_hello = 1, server_hello = 2, + new_session_ticket = 4, certificate = 11, server_key_exchange = 12, certificate_request = 13, server_hello_done = 14, certificate_verify = 15, client_key_exchange = 16, - finished = 20 + finished = 20, } SSL3HandshakeType; typedef struct { @@ -307,4 +308,49 @@ typedef struct { SSL3Opaque verify_data[12]; } TLSFinished; +/* + * TLS1 extension related data structures and constants. + */ + +/* SessionTicket extension related data structures. */ + +/* NewSessionTicket handshake message. */ +typedef struct { + uint32 received_timestamp; + uint32 ticket_lifetime_hint; + SECItem ticket; +} NewSessionTicket; + +typedef enum { + CLIENT_AUTH_ANONYMOUS = 0, + CLIENT_AUTH_CERTIFICATE = 1 +} ClientAuthenticationType; + +typedef struct { + ClientAuthenticationType client_auth_type : 1; + union { + SSL3Opaque *certificate_list; + } identity; +} ClientIdentity; + +typedef struct { + unsigned char *key_name; + unsigned char *iv; + SECItem encrypted_state; + unsigned char *mac; +} EncryptedSessionTicket; + +/* Supported extensions. */ +typedef enum { + server_name_xtn = 0, +#ifdef NSS_ENABLE_ECC + elliptic_curves_xtn = 10, + elliptic_point_formats_xtn = 11, +#endif + session_ticket_xtn = 35 +} ExtensionType; + +#define TLS1_EX_SESS_TICKET_KEY_NAME "NSS_SESS_TICKET!" +#define TLS1_EX_SESS_TICKET_MAC_LENGTH 32 + #endif /* __ssl3proto_h_ */ diff --git a/security/nss/lib/ssl/sslerr.h b/security/nss/lib/ssl/sslerr.h index c17014c24..3c38fbefa 100644 --- a/security/nss/lib/ssl/sslerr.h +++ b/security/nss/lib/ssl/sslerr.h @@ -192,6 +192,9 @@ SSL_ERROR_UNRECOGNIZED_NAME_ALERT = (SSL_ERROR_BASE + 106), SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT = (SSL_ERROR_BASE + 107), SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT = (SSL_ERROR_BASE + 108), +SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET = (SSL_ERROR_BASE + 109), +SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET = (SSL_ERROR_BASE + 110), + SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ diff --git a/security/nss/lib/ssl/sslimpl.h b/security/nss/lib/ssl/sslimpl.h index 2d5bf3427..19eab2ee9 100644 --- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -176,7 +176,7 @@ typedef enum { SSLAppOpRead = 0, /* This makes the cert cache entry exactly 4k. */ #define SSL_MAX_CACHED_CERT_LEN 4060 -#define MAX_EXTENSION_SENDERS 3 +#define MAX_EXTENSION_SENDERS 8 #define NUM_MIXERS 9 @@ -336,6 +336,7 @@ typedef struct sslOptionsStr { unsigned int noStepDown : 1; /* 15 */ unsigned int bypassPKCS11 : 1; /* 16 */ unsigned int noLocks : 1; /* 17 */ + unsigned int enableSessionTicketExtension : 1; /* 18 */ } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -631,6 +632,10 @@ struct sslSessionIDStr { char masterValid; char clAuthValid; + /* Session ticket if we have one, is sent as an extension in the + * ClientHello message. This field is used by clients. + */ + NewSessionTicket session_ticket; } ssl3; } u; }; @@ -693,10 +698,31 @@ typedef enum { wait_server_key, wait_cert_request, wait_hello_done, + wait_new_session_ticket, idle_handshake } SSL3WaitState; /* + * TLS1 Extension related constants and data structures. + */ +typedef struct TLS1ExtensionDataStr TLS1ExtensionData; +typedef struct SessionTicketDataStr SessionTicketData; + +struct TLS1ExtensionDataStr { + /* registered callbacks that send server hello extensions */ + ssl3HelloExtensionSender serverExtensionSenders[MAX_EXTENSION_SENDERS]; + /* Keep track of the extensions that are negotiated. */ + PRUint16 numAdvertisedClientExtensions; + PRUint16 numNegotiatedExtensions; + PRUint16 advertisedClientExtensions[MAX_EXTENSION_SENDERS]; + PRUint16 negotiatedExtensions[MAX_EXTENSION_SENDERS]; + + /* SessionTicket Extension related data. */ + PRBool ticket_timestamp_verified; + PRBool empty_session_ticket; +}; + +/* ** This is the "hs" member of the "ssl3" struct. ** This entire struct is protected by ssl3HandshakeLock */ @@ -771,6 +797,13 @@ struct ssl3StateStr { PRBool initialized; SSL3HandshakeState hs; ssl3CipherSpec specs[2]; /* one is current, one is pending. */ + + /* + * TLS1 Extension related data. + */ + /* True when the current session is a stateless resume. */ + PRBool stateless_resume; + TLS1ExtensionData extension_data; }; typedef struct { @@ -799,14 +832,28 @@ typedef struct SSLWrappedSymWrappingKeyStr { PRUint16 wrapIVLen; } SSLWrappedSymWrappingKey; - - - - - - - - +typedef struct SessionTicket { + uint16 ticket_version; + SSL3ProtocolVersion ssl_version; + ssl3CipherSuite cipher_suite; + SSL3CompressionMethod compression_method; + SSLSignType authAlgorithm; + uint32 authKeyBits; + SSLKEAType keaType; + uint32 keaKeyBits; + /* + * exchKeyType and msWrapMech contain meaningful values only if + * ms_is_wrapped is true. + */ + uint8 ms_is_wrapped; + SSLKEAType exchKeyType; /* XXX(wtc): same as keaType above? */ + CK_MECHANISM_TYPE msWrapMech; + uint16 ms_length; + SSL3Opaque master_secret[48]; + ClientIdentity client_identity; + SECItem peer_cert; + uint32 timestamp; +} SessionTicket; /* * SSL2 buffers used in SSL3. @@ -965,9 +1012,6 @@ struct sslSocketStr { sslHandshakeFunc nextHandshake; /*firstHandshakeLock*/ sslHandshakeFunc securityHandshake; /*firstHandshakeLock*/ - /* registered callbacks that send server hello extensions */ - ssl3HelloExtensionSender serverExtensionSenders[MAX_EXTENSION_SENDERS]; - /* the following variable is only used with socks or other proxies. */ char * peerID; /* String uniquely identifies target server. */ @@ -1047,7 +1091,6 @@ const unsigned char * preferredCipher; }; - /* All the global data items declared here should be protected using the ** ssl_global_data_lock, which is a reader/writer lock. */ @@ -1156,6 +1199,7 @@ extern SECStatus sslBuffer_Append(sslBuffer *b, const void * data, extern void ssl2_UseClearSendFunc(sslSocket *ss); extern void ssl_ChooseSessionIDProcs(sslSecurityInfo *sec); +extern sslSessionID *ssl3_NewSessionID(sslSocket *ss, PRBool is_server); extern sslSessionID *ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID, const char *urlSvrName); extern void ssl_FreeSID(sslSessionID *sid); @@ -1399,15 +1443,55 @@ extern SECStatus ssl3_SignHashes(SSL3Hashes *hash, SECKEYPrivateKey *key, extern SECStatus ssl3_VerifySignedHashes(SSL3Hashes *hash, CERTCertificate *cert, SECItem *buf, PRBool isTLS, void *pwArg); - -/* functions that append extensions to hello messages. */ -extern PRInt32 ssl3_SendServerNameIndicationExtension( sslSocket * ss, - PRBool append, PRUint32 maxBytes); +extern SECStatus ssl3_CacheWrappedMasterSecret(sslSocket *ss, + sslSessionID *sid, ssl3CipherSpec *spec, + SSL3KEAType effectiveExchKeyType); + +/* Functions that handle ClientHello and ServerHello extensions. */ +extern SECStatus ssl3_HandleServerNameExt(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_HandleSupportedCurvesExt(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_HandleSupportedPointExt(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ClientHandleSessionTicketExt(sslSocket *ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ServerHandleSessionTicketExt(sslSocket *ss, + PRUint16 ex_type, SECItem *data); + +/* ClientHello and ServerHello extension senders. + * Note that not all extension senders are exposed here; only those that + * that need exposure. + */ +extern PRInt32 ssl3_SendSessionTicketExt(sslSocket *ss, PRBool append, + PRUint32 maxBytes); +#ifdef NSS_ENABLE_ECC +extern PRInt32 ssl3_SendSupportedCurvesExt(sslSocket *ss, + PRBool append, PRUint32 maxBytes); +extern PRInt32 ssl3_SendSupportedPointExt(sslSocket *ss, + PRBool append, PRUint32 maxBytes); +#endif /* call the registered extension handlers. */ -extern SECStatus ssl3_HandleClientHelloExtensions(sslSocket *ss, +extern SECStatus ssl3_HandleHelloExtensions(sslSocket *ss, SSL3Opaque **b, PRUint32 *length); +/* Hello Extension related routines. */ +extern PRBool ssl3_ClientExtensionAdvertised(sslSocket *ss, PRUint16 ex_type); +extern PRBool ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type); +extern SECStatus ssl3_GetSessionTicketKeysPKCS11(PK11SymKey **aes_key, + PK11SymKey **mac_key); +extern SECStatus ssl3_GetSessionTicketKeys(const unsigned char **aes_key, + PRUint32 *aes_key_length, const unsigned char **mac_key, + PRUint32 *mac_key_length); +extern SECStatus ssl3_SetSIDSessionTicket(sslSessionID *sid, + NewSessionTicket *session_ticket); +extern SECStatus ssl3_SendNewSessionTicket(sslSocket *ss); + +/* Tell clients to consider tickets valid for this long. */ +#define TLS1_EX_SESS_TICKET_LIFETIME_HINT (2 * 24 * 60 * 60) /* 2 days */ +#define TLS1_EX_SESS_TICKET_VERSION (0x0100) + /* Construct a new NSPR socket for the app to use */ extern PRFileDesc *ssl_NewPRSocket(sslSocket *ss, PRFileDesc *fd); extern void ssl_FreePRSocket(PRFileDesc *fd); diff --git a/security/nss/lib/ssl/sslnonce.c b/security/nss/lib/ssl/sslnonce.c index e2dbcf0a5..c674e671c 100644 --- a/security/nss/lib/ssl/sslnonce.c +++ b/security/nss/lib/ssl/sslnonce.c @@ -47,6 +47,9 @@ #include "sslproto.h" #include "nssilock.h" #include "nsslocks.h" + +#include "pk11func.h" + #if (defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)) && !defined(_WIN32_WCE) #include <time.h> #endif @@ -109,7 +112,10 @@ ssl_DestroySID(sslSessionID *sid) if ( sid->localCert ) { CERT_DestroyCertificate(sid->localCert); } - + if (sid->u.ssl3.session_ticket.ticket.data) { + SECITEM_FreeItem(&sid->u.ssl3.session_ticket.ticket, PR_FALSE); + } + PORT_ZFree(sid, sizeof(sslSessionID)); } @@ -245,8 +251,18 @@ CacheSID(sslSessionID *sid) PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len)); } else { - if (sid->u.ssl3.sessionIDLength == 0) + if (sid->u.ssl3.sessionIDLength == 0 && + sid->u.ssl3.session_ticket.ticket.data == NULL) return; + /* Client generates the SessionID if this was a stateless resume. */ + if (sid->u.ssl3.sessionIDLength == 0) { + SECStatus rv; + rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, + SSL3_SESSIONID_BYTES); + if (rv != SECSuccess) + return; + sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES; + } expirationPeriod = ssl3_sid_timeout; PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength)); @@ -373,3 +389,35 @@ ssl_Time(void) return myTime; } +SECStatus +ssl3_SetSIDSessionTicket(sslSessionID *sid, NewSessionTicket *session_ticket) +{ + SECStatus rv; + + /* We need to lock the cache, as this sid might already be in the cache. */ + LOCK_CACHE; + + /* A server might have sent us an empty ticket, which has the + * effect of clearing the previously known ticket. + */ + if (sid->u.ssl3.session_ticket.ticket.data) + SECITEM_FreeItem(&sid->u.ssl3.session_ticket.ticket, PR_FALSE); + if (session_ticket->ticket.len > 0) { + rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.session_ticket.ticket, + &session_ticket->ticket); + if (rv != SECSuccess) { + UNLOCK_CACHE; + return rv; + } + } else { + sid->u.ssl3.session_ticket.ticket.data = NULL; + sid->u.ssl3.session_ticket.ticket.len = 0; + } + sid->u.ssl3.session_ticket.received_timestamp = + session_ticket->received_timestamp; + sid->u.ssl3.session_ticket.ticket_lifetime_hint = + session_ticket->ticket_lifetime_hint; + + UNLOCK_CACHE; + return SECSuccess; +} diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c index f51cd2446..4354eece6 100644 --- a/security/nss/lib/ssl/sslsock.c +++ b/security/nss/lib/ssl/sslsock.c @@ -178,6 +178,7 @@ static sslOptions ssl_defaults = { PR_FALSE, /* noStepDown */ PR_FALSE, /* bypassPKCS11 */ PR_FALSE, /* noLocks */ + PR_FALSE, /* enableSessionTicketExtension */ }; sslSessionIDLookupFunc ssl_sid_lookup; @@ -699,6 +700,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRBool on) } break; + case SSL_ENABLE_SESSION_TICKET_EXTENSION: + ss->opt.enableSessionTicketExtension = on; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -754,7 +759,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRBool *pOn) case SSL_NO_STEP_DOWN: on = ss->opt.noStepDown; break; case SSL_BYPASS_PKCS11: on = ss->opt.bypassPKCS11; break; case SSL_NO_LOCKS: on = ss->opt.noLocks; break; - + case SSL_ENABLE_SESSION_TICKET_EXTENSION: + on = ss->opt.enableSessionTicketExtension; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -795,6 +802,9 @@ SSL_OptionGetDefault(PRInt32 which, PRBool *pOn) case SSL_NO_STEP_DOWN: on = ssl_defaults.noStepDown; break; case SSL_BYPASS_PKCS11: on = ssl_defaults.bypassPKCS11; break; case SSL_NO_LOCKS: on = ssl_defaults.noLocks; break; + case SSL_ENABLE_SESSION_TICKET_EXTENSION: + on = ssl_defaults.enableSessionTicketExtension; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); @@ -922,6 +932,10 @@ SSL_OptionSetDefault(PRInt32 which, PRBool on) } break; + case SSL_ENABLE_SESSION_TICKET_EXTENSION: + ssl_defaults.enableSessionTicketExtension = on; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; @@ -2176,4 +2190,3 @@ loser: } return ss; } - diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h index 0fefa5782..65ae1859a 100644 --- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -59,6 +59,11 @@ typedef struct SSL3StatisticsStr { long hch_sid_cache_hits; long hch_sid_cache_misses; long hch_sid_cache_not_ok; + + /* statistics related to stateless resume */ + long sch_sid_stateless_resumes; + long hsh_sid_stateless_resumes; + long hch_sid_stateless_resumes; } SSL3Statistics; /* Key Exchange algorithm values */ diff --git a/security/nss/tests/ssl/sslstress.txt b/security/nss/tests/ssl/sslstress.txt index 97f67c207..4a1a211ee 100644 --- a/security/nss/tests/ssl/sslstress.txt +++ b/security/nss/tests/ssl/sslstress.txt @@ -8,13 +8,15 @@ noECC 0 _ -c_1000_-C_A Stress SSL2 RC4 128 with MD5 noECC 0 _ -c_1000_-C_c_-T Stress SSL3 RC4 128 with MD5 noECC 0 _ -c_1000_-C_c Stress TLS RC4 128 with MD5 + noECC 0 -u -2_-c_1000_-C_c_-u Stress TLS RC4 128 with MD5 (session ticket) # # add client auth versions here... # - noECC 0 -r_-r -c_100_-C_A_-N_-n_TestUser Stress SSL2 RC4 128 with MD5 (client auth) - noECC 0 -r_-r -c_100_-C_c_-T_-N_-n_TestUser Stress SSL3 RC4 128 with MD5 (client auth) - noECC 0 -r_-r -c_100_-C_c_-N_-n_TestUser Stress TLS RC4 128 with MD5 (client auth) + noECC 0 -r_-r -c_100_-C_A_-N_-n_TestUser Stress SSL2 RC4 128 with MD5 (no reuse, client auth) + noECC 0 -r_-r -c_100_-C_c_-T_-N_-n_TestUser Stress SSL3 RC4 128 with MD5 (no reuse, client auth) + noECC 0 -r_-r -c_100_-C_c_-N_-n_TestUser Stress TLS RC4 128 with MD5 (no reuse, client auth) + noECC 0 -r_-r_-u -2_-c_100_-C_c_-n_TestUser_-u Stress TLS RC4 128 with MD5 (session ticket, client auth) # # ############################ ECC ciphers ############################ @@ -24,6 +26,7 @@ ECC 0 -c_:C004 -2_-c_100_-C_:C004_-N Stress TLS ECDH-ECDSA AES 128 CBC with SHA (no reuse) ECC 0 -c_:C00E -2_-c_100_-C_:C00E_-N Stress TLS ECDH-RSA AES 128 CBC with SHA (no reuse) ECC 0 -c_:C013 -2_-c_1000_-C_:C013 Stress TLS ECDHE-RSA AES 128 CBC with SHA + ECC 0 -c_:C004_-u -2_-c_1000_-C_:C004_-u Stress TLS ECDH-ECDSA AES 128 CBC with SHA (session ticket) # # add client auth versions here... # @@ -32,3 +35,4 @@ ECC 0 -r_-r_-c_:C004 -c_10_-C_:C004_-N_-n_TestUser-ec Stress TLS ECDH-ECDSA AES 128 CBC with SHA (no reuse, client auth) ECC 0 -r_-r_-c_:C00E -c_10_-C_:C00E_-N_-n_TestUser-ecmixed Stress TLS ECDH-RSA AES 128 CBC with SHA (no reuse, client auth) ECC 0 -r_-r_-c_:C013 -c_100_-C_:C013_-n_TestUser-ec Stress TLS ECDHE-RSA AES 128 CBC with SHA(client auth) + ECC 0 -r_-r_-c_:C013_-u -2_-c_100_-C_:C013_-n_TestUser-ec_-u Stress TLS ECDHE-RSA AES 128 CBC with SHA(session ticket, client auth) |