Bug 665814: Prevent chosen plaintext attacks on SSL 3.0 and TLS 1.0 connections, r=wtc, sr=rrelyea
--- 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 *