diff options
author | wtc%google.com <devnull@localhost> | 2008-03-06 20:16:24 +0000 |
---|---|---|
committer | wtc%google.com <devnull@localhost> | 2008-03-06 20:16:24 +0000 |
commit | b3060348b18a759fa4a497e627ff63116ae9f449 (patch) | |
tree | 24c2b65d6e66e4591d041e949d3b7df8bbb086dc | |
parent | 95b8f2c393895ceca4a5a3aaf6528668490b4720 (diff) | |
download | nss-hg-b3060348b18a759fa4a497e627ff63116ae9f449.tar.gz |
Bug 403563: implement the TLS session ticket extension (rfc4507bis). The
patch is contributed by Nagendra Modadugu <ngm+mozilla@google.com>. A
small portion (PKCS #11 code and tests) was written by Wan-Teh Chang
<wtc@google.com>. r=nelson,wtc
Modified Files:
cmd/lib/SSLerrs.h 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/sslsnce.c
lib/ssl/sslsock.c lib/ssl/sslt.h tests/ssl/sslstress.txt
Added Files:
lib/ssl/ssl3ext.c
-rw-r--r-- | security/nss/cmd/lib/SSLerrs.h | 6 | ||||
-rw-r--r-- | security/nss/cmd/selfserv/selfserv.c | 40 | ||||
-rw-r--r-- | security/nss/cmd/strsclnt/strsclnt.c | 56 | ||||
-rw-r--r-- | security/nss/cmd/tstclnt/tstclnt.c | 28 | ||||
-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 | 265 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3ecc.c | 205 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3ext.c | 1267 | ||||
-rw-r--r-- | security/nss/lib/ssl/ssl3prot.h | 53 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslerr.h | 3 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslimpl.h | 118 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslnonce.c | 48 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslsnce.c | 236 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslsock.c | 15 | ||||
-rw-r--r-- | security/nss/lib/ssl/sslt.h | 6 | ||||
-rw-r--r-- | security/nss/tests/ssl/sslstress.txt | 10 |
17 files changed, 2068 insertions, 291 deletions
diff --git a/security/nss/cmd/lib/SSLerrs.h b/security/nss/cmd/lib/SSLerrs.h index 376ee8399..023524929 100644 --- a/security/nss/cmd/lib/SSLerrs.h +++ b/security/nss/cmd/lib/SSLerrs.h @@ -384,3 +384,9 @@ ER3(SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT , (SSL_ERROR_BASE + 107), ER3(SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT , (SSL_ERROR_BASE + 108), "SSL peer reported bad certificate hash value.") + +ER3(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET, (SSL_ERROR_BASE + 109), +"SSL received an unexpected New Session Ticket handshake message.") + +ER3(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET, (SSL_ERROR_BASE + 110), +"SSL received a malformed New Session Ticket handshake message.") diff --git a/security/nss/cmd/selfserv/selfserv.c b/security/nss/cmd/selfserv/selfserv.c index af4bc752c..1ea1cd499 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 TLS.\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,29 @@ 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, %ld ticket parse failures\n", + ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses, + ssl3stats->hch_sid_cache_not_ok, ssl3stats->hch_sid_stateless_resumes, + ssl3stats->hch_sid_ticket_parse_failures); +} + 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 && @@ -660,6 +671,7 @@ PRBool disableStepDown = PR_FALSE; PRBool bypassPKCS11 = PR_FALSE; PRBool disableLocking = PR_FALSE; PRBool testbypass = PR_FALSE; +PRBool enableSessionTickets = PR_FALSE; static const char stopCmd[] = { "GET /stop " }; static const char getCmd[] = { "GET " }; @@ -1447,6 +1459,12 @@ server_main( errExit("error disabling SSL socket locking "); } } + if (enableSessionTickets) { + rv = SSL_OptionSet(model_sock, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); + if (rv != SECSuccess) { + errExit("error enabling Session Ticket extension "); + } + } for (kea = kt_rsa; kea < kt_kea_size; kea++) { if (cert[kea] != NULL) { @@ -1693,6 +1711,7 @@ main(int argc, char **argv) char emptyString[] = { "" }; char* certPrefix = emptyString; PRUint32 protos = 0; + SSL3Statistics *ssl3stats; tmp = strrchr(argv[0], '/'); tmp = tmp ? tmp + 1 : argv[0]; @@ -1705,7 +1724,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) { @@ -1784,6 +1803,8 @@ main(int argc, char **argv) if ( maxThreads < MIN_THREADS ) maxThreads = MIN_THREADS; break; + case 'u': enableSessionTickets = PR_TRUE; break; + case 'v': verbose++; break; case 'w': passwd = PORT_Strdup(optstate->value); break; @@ -2093,6 +2114,13 @@ main(int argc, char **argv) VLOG(("selfserv: server_thread: exiting")); cleanup: + printSSLStatistics(); + ssl3stats = SSL_GetStatistics(); + if (ssl3stats->hch_sid_ticket_parse_failures != 0) { + fprintf(stderr, "selfserv: Experienced ticket parse failure(s)\n"); + exit(1); + } + { int i; for (i=0; i<kt_kea_size; i++) { diff --git a/security/nss/cmd/strsclnt/strsclnt.c b/security/nss/cmd/strsclnt/strsclnt.c index 97e1b1ae9..bc7232ea3 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 enableSessionTickets = 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); } @@ -1233,6 +1237,12 @@ client_main( } } + if (enableSessionTickets) { + rv = SSL_OptionSet(model_sock, SSL_ENABLE_SESSION_TICKETS, PR_TRUE); + if (rv != SECSuccess) + errExit("SSL_OptionSet SSL_ENABLE_SESSION_TICKETS"); + } + SSL_SetURL(model_sock, hostName); SSL_AuthCertificateHook(model_sock, mySSLAuthCertificate, @@ -1338,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) { @@ -1384,7 +1394,9 @@ main(int argc, char **argv) max_threads = active_threads = tmpInt; break; - case 'v': verbose++; break; + case 'u': enableSessionTickets = PR_TRUE; break; + + case 'v': verbose++; break; case 'w': passwd = PL_strdup(optstate->value); break; @@ -1481,29 +1493,41 @@ 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) || - (ssl3stats->hsh_sid_cache_not_ok != 0) || - (certsTested > 1); - else { + if (!NoReuse) { + if (enableSessionTickets) + exitVal = (ssl3stats->hsh_sid_stateless_resumes == 0); + else + exitVal = (ssl3stats->hsh_sid_cache_misses > 1) || + (ssl3stats->hsh_sid_stateless_resumes != 0); + if (!exitVal) + exitVal = (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); diff --git a/security/nss/cmd/tstclnt/tstclnt.c b/security/nss/cmd/tstclnt/tstclnt.c index 9c763bd89..6377c0236 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; @@ -182,22 +183,26 @@ void printSecurityInfo(PRFileDesc *fd) cert = NULL; } fprintf(stderr, - "%ld cache hits; %ld cache misses, %ld cache not reusable\n", + "%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); } 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 +223,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, @@ -512,6 +519,7 @@ int main(int argc, char **argv) int bypassPKCS11 = 0; int disableLocking = 0; int useExportPolicy = 0; + int enableSessionTickets = 0; PRSocketOptionData opt; PRNetAddr addr; PRPollDesc pollset[2]; @@ -540,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 '?': @@ -580,8 +588,12 @@ int main(int argc, char **argv) case 's': disableLocking = 1; break; + case 'u': enableSessionTickets = PR_TRUE; break; + case 'v': verbose++; break; + case 'r': renegotiate = atoi(optstate->value); break; + case 'w': password = PORT_Strdup(optstate->value); useCommandLinePassword = PR_TRUE; @@ -824,6 +836,12 @@ int main(int argc, char **argv) return 1; } + /* enable Session Ticket extension. */ + rv = SSL_OptionSet(s, SSL_ENABLE_SESSION_TICKETS, enableSessionTickets); + 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 2be497569..aca8cf8b4 100644 --- a/security/nss/lib/ssl/ssl.h +++ b/security/nss/lib/ssl/ssl.h @@ -113,7 +113,7 @@ SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd); #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_TICKETS 18 /* Enable TLS SessionTicket */ - /* extension (not implemented) */ + /* 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 b06bd306f..0197b3bb3 100644 --- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -77,7 +77,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); @@ -529,7 +528,7 @@ typedef struct tooLongStr { #endif } tooLong; -static void SSL_AtomicIncrementLong(long * x) +void SSL_AtomicIncrementLong(long * x) { if ((sizeof *x) == sizeof(PRInt32)) { PR_AtomicIncrement((PRInt32 *)x); @@ -3501,6 +3500,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->xtnData, 0, sizeof(TLSExtensionData)); + SSL_TRC(30,("%d: SSL3[%d]: reset handshake hashes", SSL_GETPID(), ss->fd )); rv = ssl3_RestartHandshakeHashes(ss); @@ -3566,6 +3570,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.sessionTicket.ticket.data) + SSL_AtomicIncrementLong(& ssl3stats.sch_sid_stateless_resumes ); + rv = ssl3_NegotiateVersion(ss, sid->version); if (rv != SECSuccess) return rv; /* error code was set */ @@ -4652,11 +4661,21 @@ ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) } ss->ssl3.hs.compression = (SSL3CompressionMethod)temp; -#ifdef DISALLOW_SERVER_HELLO_EXTENSIONS - if (length != 0) { /* malformed */ - goto alert_loser; + /* Note that if !isTLS && length != 0, we do NOT goto alert_loser. + * There are some old SSL 3.0 implementations that do send stuff + * after the end of the server hello, and we deliberately ignore + * such stuff in the interest of maximal interoperability (being + * "generous in what you accept"). + */ + 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; } -#endif /* Any errors after this point are not "malformed" errors. */ desc = handshake_failure; @@ -4764,7 +4783,17 @@ 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.sessionTicket.ticket.data != NULL) + SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_stateless_resumes ); + + if (ssl3_ExtensionNegotiated(ss, session_ticket_xtn)) + ss->ssl3.hs.ws = wait_new_session_ticket; + else + ss->ssl3.hs.ws = wait_change_cipher; + ss->ssl3.hs.isResuming = PR_TRUE; /* copy the peer cert from the SID */ @@ -5359,7 +5388,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: @@ -5400,7 +5432,7 @@ ssl3_SendHelloRequest(sslSocket *ss) * ssl3_HandleClientHello() * ssl3_HandleV2ClientHello() */ -static sslSessionID * +sslSessionID * ssl3_NewSessionID(sslSocket *ss, PRBool is_server) { sslSessionID *sid; @@ -5549,8 +5581,12 @@ ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) return rv; /* error code is set. */ } - memset(&ss->serverExtensionSenders[0], 0, - sizeof ss->serverExtensionSenders); + /* We might be starting session renegotiation in which case we should + * clear previous state. + */ + PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData)); + ss->statelessResume = PR_FALSE; + rv = ssl3_InitState(ss); if (rv != SECSuccess) { return rv; /* ssl3_InitState has set the error code. */ @@ -5587,21 +5623,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) { @@ -5616,6 +5637,84 @@ ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length) desc = handshake_failure; + /* Handle TLS hello extensions for SSL3 & TLS. We do not know if + * we are restarting a previous session until extensions have been + * parsed, since we might have received a SessionTicket extension. + * Note: we allow extensions even when negotiating SSL3 for the sake + * of interoperability (and backwards compatibility). + */ + + 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_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->xtnData.emptySessionTicket) { + 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->statelessResume) { + /* 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_SendSessionTicketXtn); + } + if (sid != NULL) { /* We've found a session cache entry for this client. * Now, if we're going to require a client-auth cert, @@ -5691,26 +5790,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); @@ -5855,6 +5934,8 @@ compression_found: * XXX make sure compression still matches */ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_hits ); + if (ss->statelessResume) + SSL_AtomicIncrementLong(& ssl3stats.hch_sid_stateless_resumes ); ss->ssl3.hs.isResuming = PR_TRUE; ss->sec.authAlgorithm = sid->authAlgorithm; @@ -5995,8 +6076,7 @@ ssl3_HandleV2ClientHello(sslSocket *ss, unsigned char *buffer, int length) ssl_GetSSL3HandshakeLock(ss); - memset(&ss->serverExtensionSenders[0], 0, - sizeof ss->serverExtensionSenders); + PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData)); rv = ssl3_InitState(ss); if (rv != SECSuccess) { @@ -6162,12 +6242,12 @@ ssl3_SendServerHello(sslSocket *ss) sid = ss->sec.ci.sid; extensions_len = ssl3_CallHelloExtensionSenders(ss, PR_FALSE, maxBytes, - &ss->serverExtensionSenders[0]); + &ss->xtnData.serverSenders[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) { @@ -6214,7 +6294,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->xtnData.serverSenders[0]); PORT_Assert(sent_len == extensions_len); if (sent_len != extensions_len) { if (sent_len >= 0) @@ -6804,6 +6884,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; @@ -7459,17 +7584,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; @@ -7531,7 +7656,7 @@ ssl3_CacheWrappedMasterSecret(sslSocket *ss, SSL3KEAType effectiveExchKeyType) wmsItem.data = sid->u.ssl3.keys.wrapped_master_secret; wmsItem.len = sizeof sid->u.ssl3.keys.wrapped_master_secret; rv = PK11_WrapSymKey(mechanism, NULL, wrappingKey, - ss->ssl3.crSpec->master_secret, &wmsItem); + spec->master_secret, &wmsItem); /* rv is examined below. */ sid->u.ssl3.keys.wrapped_master_secret_len = wmsItem.len; PK11_FreeSymKey(wrappingKey); @@ -7605,6 +7730,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. */ @@ -7679,7 +7818,9 @@ 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); /*************************************/ @@ -7842,6 +7983,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; @@ -8271,6 +8420,8 @@ ssl3_InitState(sslSocket *ss) #endif ssl_ReleaseSpecWriteLock(ss); + PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData)); + 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 d404d4f9a..38144df8b 100644 --- a/security/nss/lib/ssl/ssl3ecc.c +++ b/security/nss/lib/ssl/ssl3ecc.c @@ -462,7 +462,6 @@ ssl3_GetCurveNameForServerSocket(sslSocket *ss) ECName ec_curve = ec_noName; int signatureKeyStrength = 521; int requiredECCbits = ss->sec.secretKeyBits * 2; - int i; if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa) { svrPublicKey = SSL_GET_SERVER_PUBLIC_KEY(ss, kt_ecdh); @@ -1047,7 +1046,7 @@ static const PRUint8 ECPtFmt[6] = { * which says that we support all TLS-defined named curves. */ PRInt32 -ssl3_SendSupportedEllipticCurvesExtension( +ssl3_SendSupportedCurvesXtn( sslSocket * ss, PRBool append, PRUint32 maxBytes) @@ -1056,6 +1055,13 @@ 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) { + TLSExtensionData *xtnData = &ss->xtnData; + xtnData->advertised[xtnData->numAdvertised++] = + elliptic_curves_xtn; + } } return (sizeof EClist); } @@ -1064,7 +1070,7 @@ ssl3_SendSupportedEllipticCurvesExtension( * which says that we only support uncompressed points. */ PRInt32 -ssl3_SendSupportedPointFormatsExtension( +ssl3_SendSupportedPointFormatsXtn( sslSocket * ss, PRBool append, PRUint32 maxBytes) @@ -1073,6 +1079,13 @@ 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) { + TLSExtensionData *xtnData = &ss->xtnData; + xtnData->advertised[xtnData->numAdvertised++] = + ec_point_formats_xtn; + } } return (sizeof ECPtFmt); } @@ -1080,9 +1093,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_HandleSupportedPointFormatsXtn(sslSocket *ss, PRUint16 ex_type, + SECItem *data) { int i; @@ -1096,7 +1109,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_SendSupportedPointFormatsXtn); return rv; } } @@ -1127,9 +1140,8 @@ 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_HandleSupportedCurvesXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data) { PRInt32 list_len; PRUint32 peerCurves = 0; @@ -1180,176 +1192,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..0857e5186 --- /dev/null +++ b/security/nss/lib/ssl/ssl3ext.c @@ -0,0 +1,1267 @@ +/* + * 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 ssl3ecc.c */ +/* $Id$ */ + +#include "nssrenam.h" +#include "nss.h" +#include "ssl.h" +#include "sslimpl.h" +#include "pk11pub.h" +#include "blapi.h" +#include "prinit.h" + +static unsigned char key_name[SESS_TICKET_KEY_NAME_LEN]; +static PK11SymKey *session_ticket_enc_key_pkcs11 = NULL; +static PK11SymKey *session_ticket_mac_key_pkcs11 = NULL; + +static unsigned char session_ticket_enc_key[32]; +static unsigned char session_ticket_mac_key[SHA256_LENGTH]; + +static PRBool session_ticket_keys_initialized = PR_FALSE; +static PRCallOnceType generate_session_keys_once; + +static PRInt32 ssl3_SendServerNameXtn(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); +static SECStatus ssl3_GetSessionTicketKeysPKCS11(sslSocket *ss, + PK11SymKey **aes_key, PK11SymKey **mac_key); +static SECStatus ssl3_GetSessionTicketKeys(const unsigned char **aes_key, + PRUint32 *aes_key_length, const unsigned char **mac_key, + PRUint32 *mac_key_length); + +/* + * 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++ = (uint8) (num >> 24); + case 3: + *p++ = (uint8) (num >> 16); + case 2: + *p++ = (uint8) (num >> 8); + case 1: + *p = (uint8) num; + } + 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; + } + PORT_Memset(&generate_session_keys_once, 0, + sizeof(generate_session_keys_once)); + return SECSuccess; +} + + +static PRStatus +ssl3_GenerateSessionTicketKeysPKCS11(void *data) +{ + SECStatus rv; + sslSocket *ss = (sslSocket *)data; + SECKEYPrivateKey *svrPrivKey = ss->serverCerts[kt_rsa].SERVERKEY; + SECKEYPublicKey *svrPubKey = ss->serverCerts[kt_rsa].serverKeyPair->pubKey; + + if (svrPrivKey == NULL || svrPubKey == NULL) { + SSL_DBG(("%d: SSL[%d]: Pub or priv key(s) is NULL.", + SSL_GETPID(), ss->fd)); + goto loser; + } + + /* Get a copy of the session keys from shared memory. */ + PORT_Memcpy(key_name, SESS_TICKET_KEY_NAME_PREFIX, + sizeof(SESS_TICKET_KEY_NAME_PREFIX)); + if (!ssl_GetSessionTicketKeysPKCS11(svrPrivKey, svrPubKey, + ss->pkcs11PinArg, &key_name[SESS_TICKET_KEY_NAME_PREFIX_LEN], + &session_ticket_enc_key_pkcs11, &session_ticket_mac_key_pkcs11)) + return PR_FAILURE; + + rv = NSS_RegisterShutdown(ssl3_SessionTicketShutdown, NULL); + if (rv != SECSuccess) + goto loser; + + return PR_SUCCESS; + +loser: + ssl3_SessionTicketShutdown(NULL, NULL); + return PR_FAILURE; +} + +static SECStatus +ssl3_GetSessionTicketKeysPKCS11(sslSocket *ss, PK11SymKey **aes_key, + PK11SymKey **mac_key) +{ + if (PR_CallOnceWithArg(&generate_session_keys_once, + ssl3_GenerateSessionTicketKeysPKCS11, ss) != PR_SUCCESS) + return SECFailure; + + if (session_ticket_enc_key_pkcs11 == NULL || + session_ticket_mac_key_pkcs11 == NULL) + return SECFailure; + + *aes_key = session_ticket_enc_key_pkcs11; + *mac_key = session_ticket_mac_key_pkcs11; + return SECSuccess; +} + +static PRStatus +ssl3_GenerateSessionTicketKeys(void) +{ + PORT_Memcpy(key_name, SESS_TICKET_KEY_NAME_PREFIX, + sizeof(SESS_TICKET_KEY_NAME_PREFIX)); + + if (!ssl_GetSessionTicketKeys(&key_name[SESS_TICKET_KEY_NAME_PREFIX_LEN], + session_ticket_enc_key, session_ticket_mac_key)) + return PR_FAILURE; + + session_ticket_keys_initialized = PR_TRUE; + return PR_SUCCESS; +} + +static SECStatus +ssl3_GetSessionTicketKeys(const unsigned char **aes_key, + PRUint32 *aes_key_length, const unsigned char **mac_key, + PRUint32 *mac_key_length) +{ + if (PR_CallOnce(&generate_session_keys_once, + ssl3_GenerateSessionTicketKeys) != SECSuccess) + return SECFailure; + + if (!session_ticket_keys_initialized) + 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 clientHelloHandlers[] = { + { server_name_xtn, &ssl3_HandleServerNameXtn }, +#ifdef NSS_ENABLE_ECC + { elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn }, + { ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn }, +#endif + { session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn }, + { -1, NULL } +}; + +static const ssl3HelloExtensionHandler serverHelloHandlers[] = { + { server_name_xtn, &ssl3_HandleServerNameXtn }, + /* TODO: add a handler for ec_point_formats_xtn */ + { session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn }, + { -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_EXTENSIONS] = { + { server_name_xtn, &ssl3_SendServerNameXtn }, +#ifdef NSS_ENABLE_ECC + { elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn }, + { ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn }, +#else + { -1, NULL }, + { -1, NULL }, +#endif + { session_ticket_xtn, ssl3_SendSessionTicketXtn } +}; + +static PRBool +arrayContainsExtension(const PRUint16 *array, PRUint32 len, PRUint16 ex_type) +{ + int i; + for (i = 0; i < len; i++) { + if (ex_type == array[i]) + return PR_TRUE; + } + return PR_FALSE; +} + +PRBool +ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type) { + TLSExtensionData *xtnData = &ss->xtnData; + return arrayContainsExtension(xtnData->negotiated, + xtnData->numNegotiated, ex_type); +} + +static PRBool +ssl3_ClientExtensionAdvertised(sslSocket *ss, PRUint16 ex_type) { + TLSExtensionData *xtnData = &ss->xtnData; + return arrayContainsExtension(xtnData->advertised, + xtnData->numAdvertised, 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_SendServerNameXtn( + 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) { + TLSExtensionData *xtnData = &ss->xtnData; + xtnData->advertised[xtnData->numAdvertised++] = server_name_xtn; + } + } + return len + 9; +} + +/* handle an incoming SNI extension, by ignoring it. */ +SECStatus +ssl3_HandleServerNameXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data) +{ + /* TODO: if client, should verify extension_data is empty. */ + /* TODO: if server, should send empty extension_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_SendSessionTicketXtn( + sslSocket * ss, + PRBool append, + PRUint32 maxBytes) +{ + PRInt32 extension_length; + NewSessionTicket *session_ticket = NULL; + + /* Ignore the SessionTicket extension if processing is disabled. */ + if (!ss->opt.enableSessionTickets) + return 0; + + /* Empty extension length = extension_type (2-bytes) + + * length(extension_data) (2-bytes) + */ + 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) { + sslSessionID *sid = ss->sec.ci.sid; + session_ticket = &sid->u.ssl3.sessionTicket; + if (session_ticket->ticket.data) { + if (ss->xtnData.ticketTimestampVerified) { + 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->xtnData.ticketTimestampVerified = 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->xtnData.ticketTimestampVerified) { + rv = ssl3_AppendHandshakeVariable(ss, session_ticket->ticket.data, + session_ticket->ticket.len, 2); + ss->xtnData.ticketTimestampVerified = PR_FALSE; + } else { + rv = ssl3_AppendHandshakeNumber(ss, 0, 2); + } + if (rv != SECSuccess) + goto loser; + + if (!ss->sec.isServer) { + TLSExtensionData *xtnData = &ss->xtnData; + xtnData->advertised[xtnData->numAdvertised++] = session_ticket_xtn; + } + } else if (maxBytes < extension_length) { + PORT_Assert(0); + return 0; + } + return extension_length; + + loser: + ss->xtnData.ticketTimestampVerified = 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[TLS_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 = TLS_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(ss, &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 */ + + SESS_TICKET_KEY_NAME_LEN /* key_name */ + + AES_BLOCK_SIZE /* iv */ + + 2 /* length field for NewSessionTicket.ticket.encrypted_state */ + + ciphertext_length /* encrypted_state */ + + TLS_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, TLS_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, + (int *)&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, key_name, SESS_TICKET_KEY_NAME_LEN); + 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, key_name, + SESS_TICKET_KEY_NAME_LEN); + 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, key_name, SESS_TICKET_KEY_NAME_LEN); + 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_ClientHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + if (data->len != 0) + return SECFailure; + + /* Keep track of negotiated extensions. */ + ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type; + return SECSuccess; +} + +SECStatus +ssl3_ServerHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + SECStatus rv; + SECItem *decrypted_state = NULL; + SessionTicket *parsed_session_ticket = NULL; + sslSessionID *sid = NULL; + SSL3Statistics *ssl3stats; + + /* Ignore the SessionTicket extension if processing is disabled. */ + if (!ss->opt.enableSessionTickets) + return SECSuccess; + + /* Keep track of negotiated extensions. */ + ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = 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->xtnData.emptySessionTicket = PR_TRUE; + } else { + int i; + SECItem extension_data; + EncryptedSessionTicket enc_session_ticket; + unsigned char computed_mac[TLS_EX_SESS_TICKET_MAC_LENGTH]; + unsigned int computed_mac_length; + const SECHashObject *hashObj; + const unsigned char *aes_key; + const unsigned char *mac_key; + PK11SymKey *aes_key_pkcs11; + PK11SymKey *mac_key_pkcs11; + PRUint32 aes_key_length; + PRUint32 mac_key_length; + 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; + + /* Get session ticket keys. */ + if (ss->opt.bypassPKCS11) { + rv = ssl3_GetSessionTicketKeys(&aes_key, &aes_key_length, + &mac_key, &mac_key_length); + } else { + rv = ssl3_GetSessionTicketKeysPKCS11(ss, &aes_key_pkcs11, + &mac_key_pkcs11); + } + if (rv != SECSuccess) { + SSL_DBG(("%d: SSL[%d]: Unable to get/generate session ticket keys.", + SSL_GETPID(), ss->fd)); + goto loser; + } + + /* If the ticket sent by the client was generated under a key different + * from the one we have, bypass ticket processing. + */ + if (PORT_Memcmp(enc_session_ticket.key_name, key_name, + SESS_TICKET_KEY_NAME_LEN) != 0) { + SSL_DBG(("%d: SSL[%d]: Session ticket key_name sent mismatch.", + SSL_GETPID(), ss->fd)); + goto no_ticket; + } + + /* 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, 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 - TLS_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, mac_key_pkcs11, &macParam); + if (!hmac_ctx_pkcs11) { + SSL_DBG(("%d: SSL[%d]: Unable to create HMAC context: %d.", + SSL_GETPID(), ss->fd, PORT_GetError())); + goto no_ticket; + } else { + SSL_DBG(("%d: SSL[%d]: Successfully created HMAC context.", + SSL_GETPID(), ss->fd)); + } + rv = PK11_DigestBegin(hmac_ctx_pkcs11); + rv = PK11_DigestOp(hmac_ctx_pkcs11, extension_data.data, + extension_data.len - TLS_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) { + SSL_DBG(("%d: SSL[%d]: Session ticket MAC mismatch.", + SSL_GETPID(), ss->fd)); + 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, aes_key, + sizeof(session_ticket_enc_key), enc_session_ticket.iv, + NSS_AES_CBC, 0,AES_BLOCK_SIZE); + if (rv != SECSuccess) { + SSL_DBG(("%d: SSL[%d]: Unable to create AES context.", + SSL_GETPID(), ss->fd)); + 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, aes_key_pkcs11, &ivItem); + if (!aes_ctx_pkcs11) { + SSL_DBG(("%d: SSL[%d]: Unable to create AES context.", + SSL_GETPID(), ss->fd)); + goto no_ticket; + } + + rv = PK11_CipherOp(aes_ctx_pkcs11, decrypted_state->data, + (int *)&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 + + TLS_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->statelessResume = PR_TRUE; + ss->sec.ci.sid = sid; + } + } + + if (0) { +no_ticket: + SSL_DBG(("%d: SSL[%d]: Session ticket parsing failed.", + SSL_GETPID(), ss->fd)); + ssl3stats = SSL_GetStatistics(); + SSL_AtomicIncrementLong(& ssl3stats->hch_sid_ticket_parse_failures ); + 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, + SESS_TICKET_KEY_NAME_LEN) != 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, + TLS_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, and + * if present, invoke that handler. + * Servers ignore any extensions with unknown extension types. + * Clients reject any extensions with unadvertised extension types. + */ +SECStatus +ssl3_HandleHelloExtensions(sslSocket *ss, SSL3Opaque **b, PRUint32 *length) +{ + const ssl3HelloExtensionHandler * handlers = + ss->sec.isServer ? clientHelloHandlers : serverHelloHandlers; + + 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; + + /* 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; /* TODO: send unsupported_extension alert */ + + /* Check whether an extension has been sent multiple times. */ + if (ssl3_ExtensionNegotiated(ss, extension_type)) + return SECFailure; + + /* find extension_type in table of 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->xtnData.serverSenders[0]; + + for (i = 0; i < MAX_EXTENSIONS; ++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_EXTENSIONS); /* 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_EXTENSIONS; ++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..e410b1cfd 100644 --- a/security/nss/lib/ssl/ssl3prot.h +++ b/security/nss/lib/ssl/ssl3prot.h @@ -150,6 +150,7 @@ typedef enum { hello_request = 0, client_hello = 1, server_hello = 2, + new_session_ticket = 4, certificate = 11, server_key_exchange = 12, certificate_request = 13, @@ -307,4 +308,56 @@ typedef struct { SSL3Opaque verify_data[12]; } TLSFinished; +/* + * TLS 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; + union { + SSL3Opaque *certificate_list; + } identity; +} ClientIdentity; + +#define SESS_TICKET_KEY_NAME_LEN 16 +#define SESS_TICKET_KEY_NAME_PREFIX "NSS!" +#define SESS_TICKET_KEY_NAME_PREFIX_LEN 4 +#define SESS_TICKET_KEY_VAR_NAME_LEN 12 + +typedef struct { + unsigned char *key_name; + unsigned char *iv; + SECItem encrypted_state; + unsigned char *mac; +} EncryptedSessionTicket; + +/* Supported extensions. */ +/* Update MAX_EXTENSIONS whenever a new extension type is added. */ +typedef enum { + server_name_xtn = 0, +#ifdef NSS_ENABLE_ECC + elliptic_curves_xtn = 10, + ec_point_formats_xtn = 11, +#endif + session_ticket_xtn = 35 +} ExtensionType; + +#define MAX_EXTENSIONS 4 + +#define TLS_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 b0175e194..e1e2c2131 100644 --- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -174,8 +174,6 @@ 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 NUM_MIXERS 9 /* Mask of the 25 named curves we support. */ @@ -334,6 +332,7 @@ typedef struct sslOptionsStr { unsigned int noStepDown : 1; /* 15 */ unsigned int bypassPKCS11 : 1; /* 16 */ unsigned int noLocks : 1; /* 17 */ + unsigned int enableSessionTickets : 1; /* 18 */ } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -629,6 +628,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 sessionTicket; } ssl3; } u; }; @@ -691,10 +694,31 @@ typedef enum { wait_server_key, wait_cert_request, wait_hello_done, + wait_new_session_ticket, idle_handshake } SSL3WaitState; /* + * TLS extension related constants and data structures. + */ +typedef struct TLSExtensionDataStr TLSExtensionData; +typedef struct SessionTicketDataStr SessionTicketData; + +struct TLSExtensionDataStr { + /* registered callbacks that send server hello extensions */ + ssl3HelloExtensionSender serverSenders[MAX_EXTENSIONS]; + /* Keep track of the extensions that are negotiated. */ + PRUint16 numAdvertised; + PRUint16 numNegotiated; + PRUint16 advertised[MAX_EXTENSIONS]; + PRUint16 negotiated[MAX_EXTENSIONS]; + + /* SessionTicket Extension related data. */ + PRBool ticketTimestampVerified; + PRBool emptySessionTicket; +}; + +/* ** This is the "hs" member of the "ssl3" struct. ** This entire struct is protected by ssl3HandshakeLock */ @@ -797,14 +821,28 @@ typedef struct SSLWrappedSymWrappingKeyStr { PRUint16 wrapIVLen; } SSLWrappedSymWrappingKey; - - - - - - - - +typedef struct SessionTicketStr { + 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. @@ -963,9 +1001,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. */ @@ -1042,6 +1077,13 @@ const unsigned char * preferredCipher; /* SSL3 state info. Formerly was a pointer */ ssl3State ssl3; + + /* + * TLS extension related data. + */ + /* True when the current session is a stateless resume. */ + PRBool statelessResume; + TLSExtensionData xtnData; }; @@ -1154,6 +1196,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); @@ -1397,15 +1440,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, +extern SECStatus ssl3_CacheWrappedMasterSecret(sslSocket *ss, + sslSessionID *sid, ssl3CipherSpec *spec, + SSL3KEAType effectiveExchKeyType); + +/* Functions that handle ClientHello and ServerHello extensions. */ +extern SECStatus ssl3_HandleServerNameXtn(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_HandleSupportedCurvesXtn(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_HandleSupportedPointFormatsXtn(sslSocket * ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ClientHandleSessionTicketXtn(sslSocket *ss, + PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ServerHandleSessionTicketXtn(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_SendSessionTicketXtn(sslSocket *ss, PRBool append, + PRUint32 maxBytes); +#ifdef NSS_ENABLE_ECC +extern PRInt32 ssl3_SendSupportedCurvesXtn(sslSocket *ss, + PRBool append, PRUint32 maxBytes); +extern PRInt32 ssl3_SendSupportedPointFormatsXtn(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_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type); +extern SECStatus ssl3_SetSIDSessionTicket(sslSessionID *sid, + NewSessionTicket *session_ticket); +extern SECStatus ssl3_SendNewSessionTicket(sslSocket *ss); +extern PRBool ssl_GetSessionTicketKeys(unsigned char *keyName, + unsigned char *encKey, unsigned char *macKey); +extern PRBool ssl_GetSessionTicketKeysPKCS11(SECKEYPrivateKey *svrPrivKey, + SECKEYPublicKey *svrPubKey, void *pwArg, + unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey); + +/* Tell clients to consider tickets valid for this long. */ +#define TLS_EX_SESS_TICKET_LIFETIME_HINT (2 * 24 * 60 * 60) /* 2 days */ +#define TLS_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); @@ -1461,6 +1544,7 @@ extern int ssl_MapLowLevelError(int hiLevelError); extern PRUint32 ssl_Time(void); +extern void SSL_AtomicIncrementLong(long * x); SECStatus SSL_DisableDefaultExportCipherSuites(void); SECStatus SSL_DisableExportCipherSuites(PRFileDesc * fd); diff --git a/security/nss/lib/ssl/sslnonce.c b/security/nss/lib/ssl/sslnonce.c index 299486a6d..608b56394 100644 --- a/security/nss/lib/ssl/sslnonce.c +++ b/security/nss/lib/ssl/sslnonce.c @@ -40,6 +40,7 @@ #include "nssrenam.h" #include "cert.h" +#include "pk11pub.h" #include "secitem.h" #include "ssl.h" #include "nss.h" @@ -219,6 +220,9 @@ ssl_DestroySID(sslSessionID *sid) if ( sid->localCert ) { CERT_DestroyCertificate(sid->localCert); } + if (sid->u.ssl3.sessionTicket.ticket.data) { + SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); + } PORT_ZFree(sid, sizeof(sslSessionID)); } @@ -355,8 +359,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.sessionTicket.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)); @@ -483,3 +497,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.sessionTicket.ticket.data) + SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE); + if (session_ticket->ticket.len > 0) { + rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.sessionTicket.ticket, + &session_ticket->ticket); + if (rv != SECSuccess) { + UNLOCK_CACHE; + return rv; + } + } else { + sid->u.ssl3.sessionTicket.ticket.data = NULL; + sid->u.ssl3.sessionTicket.ticket.len = 0; + } + sid->u.ssl3.sessionTicket.received_timestamp = + session_ticket->received_timestamp; + sid->u.ssl3.sessionTicket.ticket_lifetime_hint = + session_ticket->ticket_lifetime_hint; + + UNLOCK_CACHE; + return SECSuccess; +} diff --git a/security/nss/lib/ssl/sslsnce.c b/security/nss/lib/ssl/sslsnce.c index ee3c65085..d884473dc 100644 --- a/security/nss/lib/ssl/sslsnce.c +++ b/security/nss/lib/ssl/sslsnce.c @@ -67,6 +67,10 @@ * sidCacheEntry sidCacheData[ numSIDCacheEntries]; * certCacheEntry certCacheData[numCertCacheEntries]; * SSLWrappedSymWrappingKey keyCacheData[kt_kea_size][SSL_NUM_WRAP_MECHS]; + * uint8 keyNameSuffix[SESS_TICKET_KEY_VAR_NAME_LEN] + * encKeyCacheEntry ticketEncKey; // Wrapped in non-bypass mode + * encKeyCacheEntry ticketMacKey; // Wrapped in non-bypass mode + * PRBool ticketKeysValid; * } cacheMemCacheData; */ #include "nssrenam.h" @@ -80,6 +84,7 @@ #include "sslproto.h" #include "pk11func.h" #include "base64.h" +#include "keyhi.h" #include <stdio.h> @@ -176,6 +181,12 @@ struct sidCacheSetStr { }; typedef struct sidCacheSetStr sidCacheSet; +struct encKeyCacheEntryStr { + PRUint8 bytes[512]; + PRInt32 length; +}; +typedef struct encKeyCacheEntryStr encKeyCacheEntry; + struct cacheDescStr { PRUint32 cacheMemSize; @@ -212,6 +223,10 @@ struct cacheDescStr { sidCacheEntry * sidCacheData; certCacheEntry * certCacheData; SSLWrappedSymWrappingKey * keyCacheData; + uint8 * ticketKeyNameSuffix; + encKeyCacheEntry * ticketEncKey; + encKeyCacheEntry * ticketMacKey; + PRUint32 * ticketKeysValid; /* Only the private copies of these pointers are valid */ char * cacheMem; @@ -997,10 +1012,27 @@ InitCache(cacheDesc *cache, int maxCacheEntries, PRUint32 ssl2_timeout, ptr = (ptrdiff_t)(cache->keyCacheData + cache->numKeyCacheEntries); ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); - cache->cacheMemSize = ptr; - cache->keyCacheSize = (char *)ptr - (char *)cache->keyCacheData; + cache->ticketKeyNameSuffix = (uint8 *)ptr; + ptr = (ptrdiff_t)(cache->ticketKeyNameSuffix + + SESS_TICKET_KEY_VAR_NAME_LEN); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketEncKey = (encKeyCacheEntry *)ptr; + ptr = (ptrdiff_t)(cache->ticketEncKey + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketMacKey = (encKeyCacheEntry *)ptr; + ptr = (ptrdiff_t)(cache->ticketMacKey + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->ticketKeysValid = (PRUint32 *)ptr; + ptr = (ptrdiff_t)(cache->ticketKeysValid + 1); + ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT); + + cache->cacheMemSize = ptr; + if (ssl2_timeout) { if (ssl2_timeout > MAX_SSL2_TIMEOUT) { ssl2_timeout = MAX_SSL2_TIMEOUT; @@ -1086,6 +1118,10 @@ InitCache(cacheDesc *cache, int maxCacheEntries, PRUint32 ssl2_timeout, *(ptrdiff_t *)(&cache->sidCacheData ) += ptr; *(ptrdiff_t *)(&cache->certCacheData) += ptr; *(ptrdiff_t *)(&cache->keyCacheData ) += ptr; + *(ptrdiff_t *)(&cache->ticketKeyNameSuffix) += ptr; + *(ptrdiff_t *)(&cache->ticketEncKey ) += ptr; + *(ptrdiff_t *)(&cache->ticketMacKey ) += ptr; + *(ptrdiff_t *)(&cache->ticketKeysValid) += ptr; /* initialize the locks */ init_time = ssl_Time(); @@ -1363,6 +1399,10 @@ SSL_InheritMPServerSIDCacheInstance(cacheDesc *cache, const char * envString) *(ptrdiff_t *)(&cache->sidCacheData ) += ptr; *(ptrdiff_t *)(&cache->certCacheData) += ptr; *(ptrdiff_t *)(&cache->keyCacheData ) += ptr; + *(ptrdiff_t *)(&cache->ticketKeyNameSuffix) += ptr; + *(ptrdiff_t *)(&cache->ticketEncKey ) += ptr; + *(ptrdiff_t *)(&cache->ticketMacKey ) += ptr; + *(ptrdiff_t *)(&cache->ticketKeysValid) += ptr; cache->cacheMemMap = my.cacheMemMap; cache->cacheMem = my.cacheMem; @@ -1593,6 +1633,198 @@ ssl_GetWrappingKey( PRInt32 symWrapMechIndex, return rv; } +/* Wrap and cache a session ticket key. */ +static PRBool +WrapTicketKey(SECKEYPublicKey *svrPubKey, PK11SymKey *symKey, + const char *keyName, encKeyCacheEntry* cacheEntry) +{ + SECItem wrappedKey = {siBuffer, NULL, 0}; + + wrappedKey.len = SECKEY_PublicKeyStrength(svrPubKey); + PORT_Assert(wrappedKey.len <= sizeof(cacheEntry->bytes)); + if (wrappedKey.len > sizeof(cacheEntry->bytes)) + return PR_FALSE; + wrappedKey.data = cacheEntry->bytes; + + if (PK11_PubWrapSymKey(CKM_RSA_PKCS, svrPubKey, symKey, &wrappedKey) + != SECSuccess) { + SSL_DBG(("%d: SSL[%s]: Unable to wrap session ticket %s.", + SSL_GETPID(), "unknown", keyName)); + return PR_FALSE; + } + cacheEntry->length = wrappedKey.len; + return PR_TRUE; +} + +static PRBool +GenerateAndWrapTicketKeys(SECKEYPublicKey *svrPubKey, void *pwArg, + unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey) +{ + PK11SlotInfo *slot; + CK_MECHANISM_TYPE mechanismArray[2]; + PK11SymKey *aesKeyTmp = NULL; + PK11SymKey *macKeyTmp = NULL; + cacheDesc *cache = &globalCache; + + if (PK11_GenerateRandom(cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN) != SECSuccess) { + SSL_DBG(("%d: SSL[%s]: Unable to generate random key name bytes.", + SSL_GETPID(), "unknown")); + goto loser; + } + + mechanismArray[0] = CKM_AES_CBC; + mechanismArray[1] = CKM_SHA256_HMAC; + + slot = PK11_GetBestSlotMultiple(mechanismArray, 2, pwArg); + if (slot) { + aesKeyTmp = PK11_KeyGen(slot, mechanismArray[0], NULL, 32, pwArg); + macKeyTmp = PK11_KeyGen(slot, mechanismArray[1], NULL, SHA256_LENGTH, + pwArg); + PK11_FreeSlot(slot); + } + + if (aesKeyTmp == NULL || macKeyTmp == NULL) { + SSL_DBG(("%d: SSL[%s]: Unable to generate session ticket keys.", + SSL_GETPID(), "unknown")); + goto loser; + } + + /* Export the keys to the shared cache in wrapped form. */ + if (!WrapTicketKey(svrPubKey, aesKeyTmp, "enc key", cache->ticketEncKey)) + goto loser; + if (!WrapTicketKey(svrPubKey, macKeyTmp, "mac key", cache->ticketMacKey)) + goto loser; + + PORT_Memcpy(keyName, cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN); + *aesKey = aesKeyTmp; + *macKey = macKeyTmp; + return PR_TRUE; + +loser: + if (aesKeyTmp) + PK11_FreeSymKey(aesKeyTmp); + if (macKeyTmp) + PK11_FreeSymKey(macKeyTmp); + return PR_FALSE; +} + +static PRBool +UnwrapCachedTicketKeys(SECKEYPrivateKey *svrPrivKey, unsigned char *keyName, + PK11SymKey **aesKey, PK11SymKey **macKey) +{ + SECItem wrappedKey = {siBuffer, NULL, 0}; + PK11SymKey *aesKeyTmp = NULL; + PK11SymKey *macKeyTmp = NULL; + cacheDesc *cache = &globalCache; + + wrappedKey.data = cache->ticketEncKey->bytes; + wrappedKey.len = cache->ticketEncKey->length; + PORT_Assert(wrappedKey.len <= sizeof(cache->ticketEncKey->bytes)); + aesKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey, + CKM_AES_CBC, CKA_DECRYPT, 0); + + wrappedKey.data = cache->ticketMacKey->bytes; + wrappedKey.len = cache->ticketMacKey->length; + PORT_Assert(wrappedKey.len <= sizeof(cache->ticketMacKey->bytes)); + macKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey, + CKM_SHA256_HMAC, CKA_SIGN, 0); + + if (aesKeyTmp == NULL || macKeyTmp == NULL) { + SSL_DBG(("%d: SSL[%s]: Unable to unwrap session ticket keys.", + SSL_GETPID(), "unknown")); + goto loser; + } + SSL_DBG(("%d: SSL[%s]: Successfully unwrapped session ticket keys.", + SSL_GETPID(), "unknown")); + + PORT_Memcpy(keyName, cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN); + *aesKey = aesKeyTmp; + *macKey = macKeyTmp; + return PR_TRUE; + +loser: + if (aesKeyTmp) + PK11_FreeSymKey(aesKeyTmp); + if (macKeyTmp) + PK11_FreeSymKey(macKeyTmp); + return PR_FALSE; +} + +PRBool +ssl_GetSessionTicketKeysPKCS11(SECKEYPrivateKey *svrPrivKey, + SECKEYPublicKey *svrPubKey, void *pwArg, + unsigned char *keyName, PK11SymKey **aesKey, + PK11SymKey **macKey) +{ + PRUint32 now = 0; + PRBool rv = PR_FALSE; + PRBool keysGenerated = PR_FALSE; + cacheDesc *cache = &globalCache; + + now = LockSidCacheLock(cache->keyCacheLock, now); + if (!now) + return rv; + + if (!*(cache->ticketKeysValid)) { + /* Keys do not exist, create them. */ + if (!GenerateAndWrapTicketKeys(svrPubKey, pwArg, keyName, + aesKey, macKey)) + goto loser; + keysGenerated = PR_TRUE; + *(cache->ticketKeysValid) = 1; + } + + rv = PR_TRUE; + + loser: + UnlockSidCacheLock(cache->keyCacheLock); + if (rv && !keysGenerated) + rv = UnwrapCachedTicketKeys(svrPrivKey, keyName, aesKey, macKey); + return rv; +} + +PRBool +ssl_GetSessionTicketKeys(unsigned char *keyName, unsigned char *encKey, + unsigned char *macKey) +{ + PRBool rv = PR_FALSE; + PRUint32 now = 0; + cacheDesc *cache = &globalCache; + + /* Grab lock. */ + now = LockSidCacheLock(cache->keyCacheLock, now); + if (!now) + return rv; + + if (!*(cache->ticketKeysValid)) { + if (PK11_GenerateRandom(cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN) != SECSuccess) + goto loser; + if (PK11_GenerateRandom(cache->ticketEncKey->bytes, 32) != SECSuccess) + goto loser; + if (PK11_GenerateRandom(cache->ticketMacKey->bytes, + SHA256_LENGTH) != SECSuccess) + goto loser; + *(cache->ticketKeysValid) = 1; + } + + rv = PR_TRUE; + + loser: + UnlockSidCacheLock(cache->keyCacheLock); + if (rv) { + PORT_Memcpy(keyName, cache->ticketKeyNameSuffix, + SESS_TICKET_KEY_VAR_NAME_LEN); + PORT_Memcpy(encKey, cache->ticketEncKey->bytes, 32); + PORT_Memcpy(macKey, cache->ticketMacKey->bytes, SHA256_LENGTH); + } + return rv; +} + /* The caller passes in the new value it wants * to set. This code tests the wrapped sym key entry in the shared memory. * If it is uninitialized, this function writes the caller's value into diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c index f51cd2446..98db73a79 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, /* enableSessionTickets */ }; sslSessionIDLookupFunc ssl_sid_lookup; @@ -699,6 +700,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRBool on) } break; + case SSL_ENABLE_SESSION_TICKETS: + ss->opt.enableSessionTickets = on; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -754,6 +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_TICKETS: + on = ss->opt.enableSessionTickets; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); @@ -795,6 +803,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_TICKETS: + on = ssl_defaults.enableSessionTickets; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); @@ -922,6 +933,10 @@ SSL_OptionSetDefault(PRInt32 which, PRBool on) } break; + case SSL_ENABLE_SESSION_TICKETS: + ssl_defaults.enableSessionTickets = on; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h index 0fefa5782..59de25955 100644 --- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -59,6 +59,12 @@ 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; + long hch_sid_ticket_parse_failures; } 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) |