Bug 665814: Prevent chosen plaintext attacks on SSL 3.0 and TLS 1.0 connections, r=wtc, sr=rrelyea
authorbsmith%mozilla.com
Sat, 01 Oct 2011 03:59:54 +0000
changeset 10155 7f7446fcc7ab4d577f8ae3b49d31ea49ace7d0e0
parent 10154 7df33fd79e916f6fe52f6ea033301e2dfbc0128a
child 10156 94303059e81c75db04090094de59e60d8e85b737
push idunknown
push userunknown
push dateunknown
reviewerswtc, rrelyea
bugs665814
Bug 665814: Prevent chosen plaintext attacks on SSL 3.0 and TLS 1.0 connections, r=wtc, sr=rrelyea
security/nss/lib/ssl/ssl.h
security/nss/lib/ssl/ssl3con.c
security/nss/lib/ssl/sslimpl.h
security/nss/lib/ssl/sslsock.c
--- a/security/nss/lib/ssl/ssl.h
+++ b/security/nss/lib/ssl/ssl.h
@@ -135,16 +135,44 @@ SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFi
 /* verifying the server's Finished message. This means that we could end up */
 /* sending data to an imposter. However, the data will be encrypted and     */
 /* only the true server can derive the session key. Thus, so long as the    */
 /* cipher isn't broken this is safe. Because of this, False Start will only */
 /* occur on RSA or DH ciphersuites where the cipher's key length is >= 80   */
 /* bits. The advantage of False Start is that it saves a round trip for     */
 /* client-speaks-first protocols when performing a full handshake.          */
 
+/* For SSL 3.0 and TLS 1.0, by default we prevent chosen plaintext attacks
+ * on SSL CBC mode cipher suites (see RFC 4346 Section F.3) by splitting
+ * non-empty application_data records into two records; the first record has
+ * only the first byte of plaintext, and the second has the rest.
+ *
+ * This only prevents the attack in the sending direction; the connection may
+ * still be vulnerable to such attacks if the peer does not implement a similar
+ * countermeasure.
+ *
+ * This protection mechanism is on by default; the default can be overridden by
+ * setting NSS_SSL_CBC_RANDOM_IV=0 in the environment prior to execution,
+ * and/or by the application setting the option SSL_CBC_RANDOM_IV to PR_FALSE.
+ *
+ * The per-record IV in TLS 1.1 and later adds one block of overhead per
+ * record, whereas this hack will add at least two blocks of overhead per
+ * record, so TLS 1.1+ will always be more efficient.
+ *
+ * Other implementations (e.g. some versions of OpenSSL, in some
+ * configurations) prevent the same attack by prepending an empty
+ * application_data record to every application_data record they send; we do
+ * not do that because some implementations cannot handle empty
+ * application_data records. Also, we only split application_data records and
+ * not other types of records, because some implementations will not accept
+ * fragmented records of some other types (e.g. some versions of NSS do not
+ * accept fragmented alerts).
+ */
+#define SSL_CBC_RANDOM_IV 23
+
 #ifdef SSL_DEPRECATED_FUNCTION 
 /* Old deprecated function names */
 SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRBool on);
 SSL_IMPORT SECStatus SSL_EnableDefault(int option, PRBool on);
 #endif
 
 /* New function names */
 SSL_IMPORT SECStatus SSL_OptionSet(PRFileDesc *fd, PRInt32 option, PRBool on);
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -2032,56 +2032,54 @@ ssl3_ClientAuthTokenPresent(sslSessionID
 	isPresent = PR_FALSE;
     } 
     if (slot) {
 	PK11_FreeSlot(slot);
     }
     return isPresent;
 }
 
+/* Caller must hold the spec read lock. */
 static SECStatus
-ssl3_CompressMACEncryptRecord(sslSocket *        ss,
+ssl3_CompressMACEncryptRecord(ssl3CipherSpec *   cwSpec,
+		              PRBool             isServer,
                               SSL3ContentType    type,
 		              const SSL3Opaque * pIn,
-		              PRUint32           contentLen)
-{
-    ssl3CipherSpec *          cwSpec;
+		              PRUint32           contentLen,
+		              sslBuffer *        wrBuf)
+{
     const ssl3BulkCipherDef * cipher_def;
-    sslBuffer      *          wrBuf 	  = &ss->sec.writeBuf;
     SECStatus                 rv;
     PRUint32                  macLen      = 0;
     PRUint32                  fragLen;
     PRUint32  p1Len, p2Len, oddLen = 0;
     PRInt32   cipherBytes =  0;
 
-    ssl_GetSpecReadLock(ss);	/********************************/
-
-    cwSpec = ss->ssl3.cwSpec;
     cipher_def = cwSpec->cipher_def;
 
     if (cwSpec->compressor) {
 	int outlen;
 	rv = cwSpec->compressor(
 	    cwSpec->compressContext, wrBuf->buf + SSL3_RECORD_HEADER_LENGTH,
 	    &outlen, wrBuf->space - SSL3_RECORD_HEADER_LENGTH, pIn, contentLen);
 	if (rv != SECSuccess)
 	    return rv;
 	pIn = wrBuf->buf + SSL3_RECORD_HEADER_LENGTH;
 	contentLen = outlen;
     }
 
     /*
      * Add the MAC
      */
-    rv = ssl3_ComputeRecordMAC( cwSpec, (PRBool)(ss->sec.isServer),
+    rv = ssl3_ComputeRecordMAC( cwSpec, isServer,
 	type, cwSpec->version, cwSpec->write_seq_num, pIn, contentLen,
 	wrBuf->buf + contentLen + SSL3_RECORD_HEADER_LENGTH, &macLen);
     if (rv != SECSuccess) {
 	ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
-	goto spec_locked_loser;
+	return SECFailure;
     }
     p1Len   = contentLen;
     p2Len   = macLen;
     fragLen = contentLen + macLen;	/* needs to be encrypted */
     PORT_Assert(fragLen <= MAX_FRAGMENT_LENGTH + 1024);
 
     /*
      * Pad the text (if we're doing a block cipher)
@@ -2124,52 +2122,46 @@ ssl3_CompressMACEncryptRecord(sslSocket 
 	rv = cwSpec->encode( cwSpec->encodeContext, 
 	    wrBuf->buf + SSL3_RECORD_HEADER_LENGTH, /* output */
 	    &cipherBytes,                           /* actual outlen */
 	    p1Len,                                  /* max outlen */
 	    pIn, p1Len);                      /* input, and inputlen */
 	PORT_Assert(rv == SECSuccess && cipherBytes == p1Len);
 	if (rv != SECSuccess || cipherBytes != p1Len) {
 	    PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
-	    goto spec_locked_loser;
+	    return SECFailure;
 	}
     }
     if (p2Len > 0) {
 	PRInt32 cipherBytesPart2 = -1;
 	rv = cwSpec->encode( cwSpec->encodeContext, 
 	    wrBuf->buf + SSL3_RECORD_HEADER_LENGTH + p1Len,
 	    &cipherBytesPart2,          /* output and actual outLen */
 	    p2Len,                             /* max outlen */
 	    wrBuf->buf + SSL3_RECORD_HEADER_LENGTH + p1Len,
 	    p2Len);                            /* input and inputLen*/
 	PORT_Assert(rv == SECSuccess && cipherBytesPart2 == p2Len);
 	if (rv != SECSuccess || cipherBytesPart2 != p2Len) {
 	    PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
-	    goto spec_locked_loser;
+	    return SECFailure;
 	}
 	cipherBytes += cipherBytesPart2;
     }	
     PORT_Assert(cipherBytes <= MAX_FRAGMENT_LENGTH + 1024);
 
     ssl3_BumpSequenceNumber(&cwSpec->write_seq_num);
 
     wrBuf->len    = cipherBytes + SSL3_RECORD_HEADER_LENGTH;
     wrBuf->buf[0] = type;
     wrBuf->buf[1] = MSB(cwSpec->version);
     wrBuf->buf[2] = LSB(cwSpec->version);
     wrBuf->buf[3] = MSB(cipherBytes);
     wrBuf->buf[4] = LSB(cipherBytes);
 
-    ssl_ReleaseSpecReadLock(ss); /************************************/
-
     return SECSuccess;
-
-spec_locked_loser:
-    ssl_ReleaseSpecReadLock(ss);
-    return SECFailure;
 }
 
 /* Process the plain text before sending it.
  * Returns the number of bytes of plaintext that were successfully sent
  * 	plus the number of bytes of plaintext that were copied into the
  *	output (write) buffer.
  * Returns SECFailure on a hard IO error, memory error, or crypto error.
  * Does NOT return SECWouldBlock.
@@ -2220,39 +2212,87 @@ ssl3_SendRecord(   sslSocket *        ss
     /* check for Token Presence */
     if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
 	PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
 	return SECFailure;
     }
 
     while (nIn > 0) {
 	PRUint32  contentLen = PR_MIN(nIn, MAX_FRAGMENT_LENGTH);
-
-	if (wrBuf->space < contentLen + SSL3_BUFFER_FUDGE) {
-	    PRInt32 newSpace = PR_MAX(wrBuf->space * 2, contentLen);
-	    newSpace = PR_MIN(newSpace, MAX_FRAGMENT_LENGTH);
-	    newSpace += SSL3_BUFFER_FUDGE;
-	    rv = sslBuffer_Grow(wrBuf, newSpace);
+	unsigned int spaceNeeded;
+	unsigned int numRecords;
+
+	ssl_GetSpecReadLock(ss);    /********************************/
+
+	if (nIn > 1 && ss->opt.cbcRandomIV &&
+	    ss->ssl3.cwSpec->version <= SSL_LIBRARY_VERSION_3_1_TLS &&
+	    type == content_application_data &&
+	    ss->ssl3.cwSpec->cipher_def->type == type_block /* CBC mode */) {
+	    /* We will split the first byte of the record into its own record,
+	     * as explained in the documentation for SSL_CBC_RANDOM_IV in ssl.h
+	     */
+	    numRecords = 2;
+	} else {
+	    numRecords = 1;
+	}
+
+	spaceNeeded = contentLen + (numRecords * SSL3_BUFFER_FUDGE);
+	if (spaceNeeded > wrBuf->space) {
+	    rv = sslBuffer_Grow(wrBuf, spaceNeeded);
 	    if (rv != SECSuccess) {
 		SSL_DBG(("%d: SSL3[%d]: SendRecord, tried to get %d bytes",
-			 SSL_GETPID(), ss->fd, newSpace));
-		return SECFailure; /* sslBuffer_Grow set a memory error code. */
+			 SSL_GETPID(), ss->fd, spaceNeeded));
+		goto spec_locked_loser; /* sslBuffer_Grow set error code. */
 	    }
 	}
 
-	rv = ssl3_CompressMACEncryptRecord( ss, type, pIn, contentLen);
+	if (numRecords == 2) {
+	    sslBuffer secondRecord;
+
+	    rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+	                                       ss->sec.isServer, type, pIn, 1,
+	                                       wrBuf);
+	    if (rv != SECSuccess)
+	        goto spec_locked_loser;
+
+	    PRINT_BUF(50, (ss, "send (encrypted) record data [1/2]:",
+	                   wrBuf->buf, wrBuf->len));
+
+	    secondRecord.buf = wrBuf->buf + wrBuf->len;
+	    secondRecord.len = 0;
+	    secondRecord.space = wrBuf->space - wrBuf->len;
+
+	    rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+	                                       ss->sec.isServer, type, pIn + 1,
+	                                       contentLen - 1, &secondRecord);
+	    if (rv == SECSuccess) {
+	        PRINT_BUF(50, (ss, "send (encrypted) record data [2/2]:",
+	                       secondRecord.buf, secondRecord.len));
+	        wrBuf->len += secondRecord.len;
+	    }
+	} else {
+	    rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+	                                       ss->sec.isServer, type, pIn,
+	                                       contentLen, wrBuf);
+	    if (rv == SECSuccess) {
+	        PRINT_BUF(50, (ss, "send (encrypted) record data:",
+	                       wrBuf->buf, wrBuf->len));
+	    }
+	}
+
+spec_locked_loser:
+	ssl_ReleaseSpecReadLock(ss); /************************************/
+
 	if (rv != SECSuccess)
 	    return SECFailure;
 
 	pIn += contentLen;
 	nIn -= contentLen;
 	PORT_Assert( nIn >= 0 );
 
-	PRINT_BUF(50, (ss, "send (encrypted) record data:", wrBuf->buf, wrBuf->len));
-
 	/* If there's still some previously saved ciphertext,
 	 * or the caller doesn't want us to send the data yet,
 	 * then add all our new ciphertext to the amount previously saved.
 	 */
 	if ((ss->pendingBuf.len > 0) ||
 	    (flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
 
 	    rv = ssl_SaveWriteData(ss, wrBuf->buf, wrBuf->len);
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -329,16 +329,17 @@ typedef struct sslOptionsStr {
     unsigned int noStepDown             : 1;  /* 15 */
     unsigned int bypassPKCS11           : 1;  /* 16 */
     unsigned int noLocks                : 1;  /* 17 */
     unsigned int enableSessionTickets   : 1;  /* 18 */
     unsigned int enableDeflate          : 1;  /* 19 */
     unsigned int enableRenegotiation    : 2;  /* 20-21 */
     unsigned int requireSafeNegotiation : 1;  /* 22 */
     unsigned int enableFalseStart       : 1;  /* 23 */
+    unsigned int cbcRandomIV            : 1;  /* 24 */
 } sslOptions;
 
 typedef enum { sslHandshakingUndetermined = 0,
 	       sslHandshakingAsClient,
 	       sslHandshakingAsServer 
 } sslHandshakingType;
 
 typedef struct sslServerCertsStr {
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -180,16 +180,17 @@ static sslOptions ssl_defaults = {
     PR_FALSE,   /* noStepDown         */
     PR_FALSE,   /* bypassPKCS11       */
     PR_FALSE,   /* noLocks            */
     PR_FALSE,   /* enableSessionTickets */
     PR_FALSE,   /* enableDeflate      */
     2,          /* enableRenegotiation (default: requires extension) */
     PR_FALSE,   /* requireSafeNegotiation */
     PR_FALSE,   /* enableFalseStart   */
+    PR_TRUE     /* cbcRandomIV        */
 };
 
 sslSessionIDLookupFunc  ssl_sid_lookup;
 sslSessionIDCacheFunc   ssl_sid_cache;
 sslSessionIDUncacheFunc ssl_sid_uncache;
 
 static PRBool ssl_inited = PR_FALSE;
 static PRDescIdentity ssl_layer_id;
@@ -730,16 +731,20 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 wh
       case SSL_REQUIRE_SAFE_NEGOTIATION:
 	ss->opt.requireSafeNegotiation = on;
 	break;
 
       case SSL_ENABLE_FALSE_START:
 	ss->opt.enableFalseStart = on;
 	break;
 
+      case SSL_CBC_RANDOM_IV:
+	ss->opt.cbcRandomIV = on;
+	break;
+
       default:
 	PORT_SetError(SEC_ERROR_INVALID_ARGS);
 	rv = SECFailure;
     }
 
     /* We can't use the macros for releasing the locks here,
      * because ss->opt.noLocks might have changed just above.
      * We must release these locks (monitors) here, if we aquired them above,
@@ -794,16 +799,17 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 wh
 	on = ss->opt.enableSessionTickets;
 	break;
     case SSL_ENABLE_DEFLATE:      on = ss->opt.enableDeflate;      break;
     case SSL_ENABLE_RENEGOTIATION:     
                                   on = ss->opt.enableRenegotiation; break;
     case SSL_REQUIRE_SAFE_NEGOTIATION: 
                                   on = ss->opt.requireSafeNegotiation; break;
     case SSL_ENABLE_FALSE_START:  on = ss->opt.enableFalseStart;   break;
+    case SSL_CBC_RANDOM_IV:       on = ss->opt.cbcRandomIV;        break;
 
     default:
 	PORT_SetError(SEC_ERROR_INVALID_ARGS);
 	rv = SECFailure;
     }
 
     ssl_ReleaseSSL3HandshakeLock(ss);
     ssl_Release1stHandshakeLock(ss);
@@ -847,16 +853,17 @@ SSL_OptionGetDefault(PRInt32 which, PRBo
 	break;
     case SSL_ENABLE_DEFLATE:      on = ssl_defaults.enableDeflate;      break;
     case SSL_ENABLE_RENEGOTIATION:     
                                   on = ssl_defaults.enableRenegotiation; break;
     case SSL_REQUIRE_SAFE_NEGOTIATION: 
                                   on = ssl_defaults.requireSafeNegotiation; 
 				  break;
     case SSL_ENABLE_FALSE_START:  on = ssl_defaults.enableFalseStart;   break;
+    case SSL_CBC_RANDOM_IV:       on = ssl_defaults.cbcRandomIV;        break;
 
     default:
 	PORT_SetError(SEC_ERROR_INVALID_ARGS);
 	rv = SECFailure;
     }
 
     *pOn = on;
     return rv;
@@ -1002,16 +1009,20 @@ SSL_OptionSetDefault(PRInt32 which, PRBo
       case SSL_REQUIRE_SAFE_NEGOTIATION:
 	ssl_defaults.requireSafeNegotiation = on;
 	break;
 
       case SSL_ENABLE_FALSE_START:
 	ssl_defaults.enableFalseStart = on;
 	break;
 
+      case SSL_CBC_RANDOM_IV:
+	ssl_defaults.cbcRandomIV = on;
+	break;
+
       default:
 	PORT_SetError(SEC_ERROR_INVALID_ARGS);
 	return SECFailure;
     }
     return SECSuccess;
 }
 
 /* function tells us if the cipher suite is one that we no longer support. */
@@ -2343,16 +2354,21 @@ ssl_SetDefaultsFromEnvironment(void)
 	               ssl_defaults.enableRenegotiation));
 	}
 	ev = getenv("NSS_SSL_REQUIRE_SAFE_NEGOTIATION");
 	if (ev && ev[0] == '1') {
 	    ssl_defaults.requireSafeNegotiation = PR_TRUE;
 	    SSL_TRACE(("SSL: requireSafeNegotiation set to %d", 
 	                PR_TRUE));
 	}
+	ev = getenv("NSS_SSL_CBC_RANDOM_IV");
+	if (ev && ev[0] == '0') {
+	    ssl_defaults.cbcRandomIV = PR_FALSE;
+	    SSL_TRACE(("SSL: cbcRandomIV set to 0"));
+	}
     }
 #endif /* NSS_HAVE_GETENV */
 }
 
 /*
 ** Create a newsocket structure for a file descriptor.
 */
 static sslSocket *