Index: net/third_party/nss/ssl/ssl3con.c |
diff --git a/net/third_party/nss/ssl/ssl3con.c b/net/third_party/nss/ssl/ssl3con.c |
index 506044750c6306ecef59d8bc217f2c048011f68f..d282dd76848624465c48f251cbd50cabff35cecc 100644 |
--- a/net/third_party/nss/ssl/ssl3con.c |
+++ b/net/third_party/nss/ssl/ssl3con.c |
@@ -1846,7 +1846,6 @@ static const unsigned char mac_pad_2 [60] = { |
}; |
/* Called from: ssl3_SendRecord() |
-** ssl3_HandleRecord() |
** Caller must already hold the SpecReadLock. (wish we could assert that!) |
*/ |
static SECStatus |
@@ -2028,6 +2027,128 @@ ssl3_ComputeRecordMAC( |
return rv; |
} |
+/* This is a bodge to allow this code to be compiled against older NSS headers |
+ * that don't contains the CBC constant-time changes. */ |
+#ifndef CKM_NSS_HMAC_CONSTANT_TIME |
+#define CKM_NSS_HMAC_CONSTANT_TIME (CKM_NSS + 19) |
+#define CKM_NSS_SSLV3_MAC_CONSTANT_TIME (CKM_NSS + 20) |
+ |
+typedef struct CK_NSS_MACConstantTimeParams { |
+ CK_MECHANISM_TYPE hashAlg; /* in */ |
+ CK_ULONG ulBodyTotalLength; /* in */ |
+ CK_BYTE * pHeader; /* in */ |
+ CK_ULONG ulHeaderLength; /* in */ |
wtc
2013/02/05 16:45:48
I'm going to suggest three naming changes here to
agl
2013/02/05 21:44:18
I have applied all your suggested naming changes.
|
+} CK_NSS_MACConstantTimeParams; |
+#endif |
+ |
+/* Called from: ssl3_HandleRecord() |
+ * Caller must already hold the SpecReadLock. (wish we could assert that!) |
+ * |
+ * On entry: |
+ * originalLen >= inputLen >= MAC size |
+*/ |
+static SECStatus |
+ssl3_ComputeRecordMACConstantTime( |
+ ssl3CipherSpec * spec, |
+ PRBool useServerMacKey, |
+ PRBool isDTLS, |
+ SSL3ContentType type, |
+ SSL3ProtocolVersion version, |
+ SSL3SequenceNumber seq_num, |
+ const SSL3Opaque * input, |
+ int inputLen, |
+ int originalLen, |
+ unsigned char * outbuf, |
+ unsigned int * outLen) |
+{ |
+ CK_MECHANISM_TYPE macType; |
+ CK_NSS_MACConstantTimeParams params; |
+ PK11Context * mac_context; |
+ SECItem param; |
+ SECStatus rv; |
+ unsigned char header[13]; |
+ PK11SymKey * key; |
+ int recordLength; |
+ |
+ PORT_Assert(inputLen >= spec->mac_size); |
+ PORT_Assert(originalLen >= inputLen); |
+ |
+ if (spec->bypassCiphers) { |
+ /* This function doesn't support PKCS#11 bypass. We fallback on the |
+ * non-constant time version. */ |
+ goto fallback; |
+ } |
+ |
+ if (spec->mac_def->mac == mac_null) { |
+ *outLen = 0; |
+ return SECSuccess; |
+ } |
+ |
+ header[0] = (unsigned char)(seq_num.high >> 24); |
+ header[1] = (unsigned char)(seq_num.high >> 16); |
+ header[2] = (unsigned char)(seq_num.high >> 8); |
+ header[3] = (unsigned char)(seq_num.high >> 0); |
+ header[4] = (unsigned char)(seq_num.low >> 24); |
+ header[5] = (unsigned char)(seq_num.low >> 16); |
+ header[6] = (unsigned char)(seq_num.low >> 8); |
+ header[7] = (unsigned char)(seq_num.low >> 0); |
+ header[8] = type; |
+ |
+ macType = CKM_NSS_HMAC_CONSTANT_TIME; |
+ recordLength = inputLen - spec->mac_size; |
+ if (spec->version <= SSL_LIBRARY_VERSION_3_0) { |
+ macType = CKM_NSS_SSLV3_MAC_CONSTANT_TIME; |
+ header[9] = recordLength >> 8; |
+ header[10] = recordLength; |
+ params.ulHeaderLength = 11; |
+ } else { |
+ header[9] = version >> 8; |
+ header[10] = version; |
wtc
2013/02/05 16:45:48
IMPORTANT: please port my isDTLS change from
https
agl
2013/02/05 21:44:18
Done.
|
+ header[11] = recordLength >> 8; |
+ header[12] = recordLength; |
+ params.ulHeaderLength = 13; |
+ } |
+ |
+ params.hashAlg = spec->mac_def->mmech; |
+ params.ulBodyTotalLength = originalLen; |
+ params.pHeader = header; |
+ |
+ param.data = (unsigned char*) ¶ms; |
+ param.len = sizeof(params); |
+ param.type = 0; |
+ |
+ key = spec->server.write_mac_key; |
+ if (!useServerMacKey) { |
+ key = spec->client.write_mac_key; |
+ } |
+ mac_context = PK11_CreateContextBySymKey(macType, CKA_SIGN, key, ¶m); |
+ if (mac_context == NULL) { |
+ /* Older versions of NSS may not support constant-time MAC. */ |
wtc
2013/02/05 16:45:48
I think this code is also appropriate for NSS upst
agl
2013/02/05 21:44:18
That's true, although it's only local memory acces
|
+ goto fallback; |
+ } |
+ |
+ rv = PK11_DigestBegin(mac_context); |
+ rv |= PK11_DigestOp(mac_context, input, inputLen); |
+ rv |= PK11_DigestFinal(mac_context, outbuf, outLen, spec->mac_size); |
+ PK11_DestroyContext(mac_context, PR_TRUE); |
+ |
+ PORT_Assert(rv != SECSuccess || *outLen == (unsigned)spec->mac_size); |
+ |
+ if (rv != SECSuccess) { |
+ rv = SECFailure; |
+ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE); |
+ } |
+ return rv; |
+ |
+fallback: |
+ /* ssl3_ComputeRecordMAC expects the MAC to have been removed from the |
+ * length already. */ |
+ inputLen -= spec->mac_size; |
+ return ssl3_ComputeRecordMAC(spec, useServerMacKey, isDTLS, type, |
+ version, seq_num, input, inputLen, |
+ outbuf, outLen); |
+} |
+ |
static PRBool |
ssl3_ClientAuthTokenPresent(sslSessionID *sid) { |
PK11SlotInfo *slot = NULL; |
@@ -10045,6 +10166,177 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) |
return SECSuccess; |
} |
+/* These macros return the given value with the MSB copied to all the other |
+ * bits. They use the fact that arithmetic shift shifts-in the sign bit. |
+ * However, this is not ensured by the C standard so you may need to replace |
+ * them with something else for odd compilers. */ |
+#define DUPLICATE_MSB_TO_ALL(x) ( (unsigned)( (int)(x) >> (sizeof(int)*8-1) ) ) |
+#define DUPLICATE_MSB_TO_ALL_8(x) ((unsigned char)(DUPLICATE_MSB_TO_ALL(x))) |
+ |
+/* SECStatusToMask returns, in constant time, a mask value of all ones if rv == |
+ * SECSuccess. Otherwise it returns zero. */ |
+static unsigned SECStatusToMask(SECStatus rv) |
+{ |
+ unsigned int good; |
+ /* rv ^ SECSuccess is zero iff rv == SECSuccess. Subtracting one results in |
+ * the MSB being set to one iff it was zero before. */ |
+ good = rv ^ SECSuccess; |
+ good--; |
+ return DUPLICATE_MSB_TO_ALL(good); |
+} |
+ |
+/* ssl_ConstantTimeGE returns 0xff if a>=b and 0x00 otherwise. */ |
+static unsigned char ssl_ConstantTimeGE(unsigned a, unsigned b) |
+{ |
+ a -= b; |
+ return DUPLICATE_MSB_TO_ALL(~a); |
+} |
+ |
+/* ssl_ConstantTimeEQ8 returns 0xff if a==b and 0x00 otherwise. */ |
+static unsigned char ssl_ConstantTimeEQ8(unsigned char a, unsigned char b) |
+{ |
+ unsigned c = a ^ b; |
+ c--; |
+ return DUPLICATE_MSB_TO_ALL_8(c); |
+} |
+ |
+static SECStatus ssl_RemoveSSLv3CBCPadding(sslBuffer *plaintext, |
+ unsigned blockSize, |
+ unsigned macSize) { |
+ unsigned int paddingLength, good, t; |
+ const unsigned int overhead = 1 /* padding length byte */ + macSize; |
+ |
+ /* These lengths are all public so we can test them in non-constant |
+ * time. */ |
+ if (overhead > plaintext->len) { |
+ return SECFailure; |
+ } |
+ |
+ paddingLength = plaintext->buf[plaintext->len-1]; |
+ /* SSLv3 padding bytes are random and cannot be checked. */ |
+ t = plaintext->len; |
+ t -= paddingLength+overhead; |
+ /* If len >= padding_length+overhead then the MSB of t is zero. */ |
+ good = DUPLICATE_MSB_TO_ALL(~t); |
+ /* SSLv3 requires that the padding is minimal. */ |
+ t = blockSize - (paddingLength+1); |
+ good &= DUPLICATE_MSB_TO_ALL(~t); |
+ plaintext->len -= good & (paddingLength+1); |
+ return (good & SECSuccess) | (~good & SECFailure); |
+} |
+ |
+ |
+static SECStatus ssl_RemoveTLSCBCPadding(sslBuffer *plaintext, |
+ unsigned macSize) { |
+ unsigned int paddingLength, good, t, toCheck, i; |
+ const unsigned int overhead = 1 /* padding length byte */ + macSize; |
+ |
+ /* These lengths are all public so we can test them in non-constant |
+ * time. */ |
+ if (overhead > plaintext->len) { |
+ return SECFailure; |
+ } |
+ |
+ paddingLength = plaintext->buf[plaintext->len-1]; |
+ t = plaintext->len; |
+ t -= paddingLength+overhead; |
+ /* If len >= paddingLength+overhead then the MSB of t is zero. */ |
+ good = DUPLICATE_MSB_TO_ALL(~t); |
+ |
+ /* The padding consists of a length byte at the end of the record and then |
+ * that many bytes of padding, all with the same value as the length byte. |
+ * Thus, with the length byte included, there are paddingLength+1 bytes of |
+ * padding. |
+ * |
+ * We can't check just |paddingLength+1| bytes because that leaks |
+ * decrypted information. Therefore we always have to check the maximum |
+ * amount of padding possible. (Again, the length of the record is |
+ * public information so we can use it.) */ |
+ toCheck = 255; /* maximum amount of padding. */ |
+ if (toCheck > plaintext->len-1) { |
+ toCheck = plaintext->len-1; |
+ } |
+ |
+ for (i = 0; i < toCheck; i++) { |
+ unsigned int t = paddingLength - i; |
+ /* If i <= paddingLength then the MSB of t is zero and mask is |
+ * 0xff. Otherwise, mask is 0. */ |
+ unsigned char mask = DUPLICATE_MSB_TO_ALL(~t); |
+ unsigned char b = plaintext->buf[plaintext->len-1-i]; |
+ /* The final |paddingLength+1| bytes should all have the value |
+ * |paddingLength|. Therefore the XOR should be zero. */ |
+ good &= ~(mask&(paddingLength ^ b)); |
+ } |
+ |
+ /* If any of the final |paddingLength+1| bytes had the wrong value, |
+ * one or more of the lower eight bits of |good| will be cleared. We |
+ * AND the bottom 8 bits together and duplicate the result to all the |
+ * bits. */ |
+ good &= good >> 4; |
+ good &= good >> 2; |
+ good &= good >> 1; |
+ good <<= sizeof(good)*8-1; |
+ good = DUPLICATE_MSB_TO_ALL(good); |
+ |
+ plaintext->len -= good & (paddingLength+1); |
+ return (good & SECSuccess) | (~good & SECFailure); |
+} |
+ |
+/* On entry: |
+ * originalLength >= macSize |
+ * macSize <= MAX_MAC_LENGTH |
+ * plaintext->len >= macSize |
+ */ |
+static void ssl_CBCExtractMAC(sslBuffer *plaintext, |
+ unsigned int originalLength, |
+ SSL3Opaque* out, |
+ unsigned int macSize) { |
+ unsigned char rotatedMac[MAX_MAC_LENGTH]; |
+ /* macEnd is the index of |plaintext->buf| just after the end of the MAC. */ |
+ unsigned macEnd = plaintext->len; |
+ unsigned macStart = macEnd - macSize; |
+ /* scanStart contains the number of bytes that we can ignore because |
+ * the MAC's position can only vary by 255 bytes. */ |
+ unsigned scanStart = 0; |
+ unsigned i, j, divSpoiler; |
+ unsigned char rotateOffset; |
+ |
+ if (originalLength > macSize + 255 + 1) |
+ scanStart = originalLength - (macSize + 255 + 1); |
+ |
+ /* divSpoiler contains a multiple of macSize that is used to cause the |
+ * modulo operation to be constant time. Without this, the time varies |
+ * based on the amount of padding when running on Intel chips at least. |
+ * |
+ * The aim of right-shifting macSize is so that the compiler doesn't |
+ * figure out that it can remove divSpoiler as that would require it |
+ * to prove that macSize is always even, which I hope is beyond it. */ |
+ divSpoiler = macSize >> 1; |
+ divSpoiler <<= (sizeof(divSpoiler)-1)*8; |
+ rotateOffset = (divSpoiler + macStart - scanStart) % macSize; |
+ |
+ memset(rotatedMac, 0, macSize); |
+ for (i = scanStart; i < originalLength;) { |
+ for (j = 0; j < macSize && i < originalLength; i++, j++) { |
+ unsigned char macStarted = ssl_ConstantTimeGE(i, macStart); |
+ unsigned char macEnded = ssl_ConstantTimeGE(i, macEnd); |
+ unsigned char b = 0; |
+ b = plaintext->buf[i]; |
+ rotatedMac[j] |= b & macStarted & ~macEnded; |
+ } |
+ } |
+ |
+ /* Now rotate the MAC. If we knew that the MAC fit into a CPU cache line we |
+ * could line-align |rotatedMac| and rotate in place. */ |
+ memset(out, 0, macSize); |
+ for (i = 0; i < macSize; i++) { |
+ unsigned char offset = (divSpoiler + macSize - rotateOffset + i) % macSize; |
+ for (j = 0; j < macSize; j++) { |
+ out[j] |= rotatedMac[i] & ssl_ConstantTimeEQ8(j, offset); |
+ } |
+ } |
+} |
+ |
/* if cText is non-null, then decipher, check MAC, and decompress the |
* SSL record from cText->buf (typically gs->inbuf) |
* into databuf (typically gs->buf), and any previous contents of databuf |
@@ -10074,15 +10366,18 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf) |
ssl3CipherSpec * crSpec; |
SECStatus rv; |
unsigned int hashBytes = MAX_MAC_LENGTH + 1; |
- unsigned int padding_length; |
PRBool isTLS; |
- PRBool padIsBad = PR_FALSE; |
SSL3ContentType rType; |
SSL3Opaque hash[MAX_MAC_LENGTH]; |
+ SSL3Opaque givenHashBuf[MAX_MAC_LENGTH]; |
+ SSL3Opaque *givenHash; |
sslBuffer *plaintext; |
sslBuffer temp_buf; |
PRUint64 dtls_seq_num; |
unsigned int ivLen = 0; |
+ unsigned int originalLen = 0; |
+ unsigned int good; |
+ unsigned int minLength; |
PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) ); |
@@ -10150,6 +10445,30 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf) |
} |
} |
+ good = (unsigned)-1; |
+ minLength = crSpec->mac_size; |
+ if (cipher_def->type == type_block) { |
+ /* CBC records have a padding length byte at the end. */ |
+ minLength++; |
+ if (crSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) { |
+ /* With >= TLS 1.1, CBC records have an explicit IV. */ |
+ minLength += cipher_def->iv_size; |
+ } |
+ } |
+ |
+ /* We can perform this test in variable time because the record's total |
+ * length and the ciphersuite are both public knowledge. */ |
+ if (cText->buf->len < minLength) { |
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, record too small.", |
+ SSL_GETPID(), ss->fd)); |
+ /* must not hold spec lock when calling SSL3_SendAlert. */ |
+ ssl_ReleaseSpecReadLock(ss); |
+ SSL3_SendAlert(ss, alert_fatal, bad_record_mac); |
+ /* always log mac error, in case attacker can read server logs. */ |
+ PORT_SetError(SSL_ERROR_BAD_MAC_READ); |
+ return SECFailure; |
+ } |
+ |
if (cipher_def->type == type_block && |
crSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) { |
/* Consume the per-record explicit IV. RFC 4346 Section 6.2.3.2 states |
@@ -10167,16 +10486,6 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf) |
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); |
return SECFailure; |
} |
- if (ivLen > cText->buf->len) { |
- SSL_DBG(("%d: SSL3[%d]: HandleRecord, IV length check failed", |
- SSL_GETPID(), ss->fd)); |
- /* must not hold spec lock when calling SSL3_SendAlert. */ |
- ssl_ReleaseSpecReadLock(ss); |
- SSL3_SendAlert(ss, alert_fatal, bad_record_mac); |
- /* always log mac error, in case attacker can read server logs. */ |
- PORT_SetError(SSL_ERROR_BAD_MAC_READ); |
- return SECFailure; |
- } |
PRINT_BUF(80, (ss, "IV (ciphertext):", cText->buf->buf, ivLen)); |
@@ -10187,12 +10496,7 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf) |
rv = crSpec->decode(crSpec->decodeContext, iv, &decoded, |
sizeof(iv), cText->buf->buf, ivLen); |
- if (rv != SECSuccess) { |
- /* All decryption failures must be treated like a bad record |
- * MAC; see RFC 5246 (TLS 1.2). |
- */ |
- padIsBad = PR_TRUE; |
- } |
+ good &= SECStatusToMask(rv); |
} |
/* If we will be decompressing the buffer we need to decrypt somewhere |
@@ -10234,54 +10538,70 @@ ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf) |
rv = crSpec->decode( |
crSpec->decodeContext, plaintext->buf, (int *)&plaintext->len, |
plaintext->space, cText->buf->buf + ivLen, cText->buf->len - ivLen); |
+ good &= SECStatusToMask(rv); |
PRINT_BUF(80, (ss, "cleartext:", plaintext->buf, plaintext->len)); |
- if (rv != SECSuccess) { |
- /* All decryption failures must be treated like a bad record |
- * MAC; see RFC 5246 (TLS 1.2). |
- */ |
- padIsBad = PR_TRUE; |
- } |
+ |
+ originalLen = plaintext->len; |
/* If it's a block cipher, check and strip the padding. */ |
- if (cipher_def->type == type_block && !padIsBad) { |
- PRUint8 * pPaddingLen = plaintext->buf + plaintext->len - 1; |
- padding_length = *pPaddingLen; |
- /* TLS permits padding to exceed the block size, up to 255 bytes. */ |
- if (padding_length + 1 + crSpec->mac_size > plaintext->len) |
- padIsBad = PR_TRUE; |
- else { |
- plaintext->len -= padding_length + 1; |
- /* In TLS all padding bytes must be equal to the padding length. */ |
- if (isTLS) { |
- PRUint8 *p; |
- for (p = pPaddingLen - padding_length; p < pPaddingLen; ++p) { |
- padIsBad |= *p ^ padding_length; |
- } |
- } |
- } |
- } |
+ if (cipher_def->type == type_block) { |
+ const unsigned int blockSize = cipher_def->iv_size; |
+ const unsigned int macSize = crSpec->mac_size; |
- /* Remove the MAC. */ |
- if (plaintext->len >= crSpec->mac_size) |
- plaintext->len -= crSpec->mac_size; |
- else |
- padIsBad = PR_TRUE; /* really macIsBad */ |
+ if (crSpec->version <= SSL_LIBRARY_VERSION_3_0) { |
+ good &= SECStatusToMask(ssl_RemoveSSLv3CBCPadding( |
+ plaintext, blockSize, macSize)); |
+ } else { |
+ good &= SECStatusToMask(ssl_RemoveTLSCBCPadding( |
+ plaintext, macSize)); |
+ } |
+ } |
/* compute the MAC */ |
rType = cText->type; |
- rv = ssl3_ComputeRecordMAC( crSpec, (PRBool)(!ss->sec.isServer), |
- IS_DTLS(ss), rType, cText->version, |
- IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num, |
- plaintext->buf, plaintext->len, hash, &hashBytes); |
- if (rv != SECSuccess) { |
- padIsBad = PR_TRUE; /* really macIsBad */ |
+ if (cipher_def->type == type_block) { |
+ rv = ssl3_ComputeRecordMACConstantTime( |
+ crSpec, (PRBool)(!ss->sec.isServer), |
+ IS_DTLS(ss), rType, cText->version, |
+ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num, |
+ plaintext->buf, plaintext->len, originalLen, |
+ hash, &hashBytes); |
+ |
+ ssl_CBCExtractMAC(plaintext, originalLen, givenHashBuf, |
+ crSpec->mac_size); |
+ givenHash = givenHashBuf; |
+ |
+ /* plaintext->len will always have enough space to remove the MAC |
+ * because in ssl_Remove{SSLv3|TLS}CBCPadding we only adjust |
+ * plaintext->len if the result has enough space for the MAC and we |
+ * tested the unadjusted size against minLength, above. */ |
+ plaintext->len -= crSpec->mac_size; |
+ } else { |
+ /* This is safe because we checked the minLength above. */ |
+ plaintext->len -= crSpec->mac_size; |
+ |
+ rv = ssl3_ComputeRecordMAC( |
+ crSpec, (PRBool)(!ss->sec.isServer), |
+ IS_DTLS(ss), rType, cText->version, |
+ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num, |
+ plaintext->buf, plaintext->len, |
+ hash, &hashBytes); |
+ |
+ /* We can read the MAC directly from the record because its location is |
+ * public when a stream cipher is used. */ |
+ givenHash = plaintext->buf + plaintext->len; |
+ } |
+ |
+ good &= SECStatusToMask(rv); |
+ |
+ if (hashBytes != (unsigned)crSpec->mac_size || |
+ NSS_SecureMemcmp(givenHash, hash, crSpec->mac_size) != 0) { |
+ /* We're allowed to leak whether or not the MAC check was correct */ |
+ good = 0; |
} |
- /* Check the MAC */ |
- if (hashBytes != (unsigned)crSpec->mac_size || padIsBad || |
- NSS_SecureMemcmp(plaintext->buf + plaintext->len, hash, |
- crSpec->mac_size) != 0) { |
+ if (good == 0) { |
/* must not hold spec lock when calling SSL3_SendAlert. */ |
ssl_ReleaseSpecReadLock(ss); |