summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/freebl/hmacct.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/freebl/hmacct.c')
-rw-r--r--security/nss/lib/freebl/hmacct.c335
1 files changed, 335 insertions, 0 deletions
diff --git a/security/nss/lib/freebl/hmacct.c b/security/nss/lib/freebl/hmacct.c
new file mode 100644
index 000000000..c7815ac05
--- /dev/null
+++ b/security/nss/lib/freebl/hmacct.c
@@ -0,0 +1,335 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef FREEBL_NO_DEPEND
+#include "stubs.h"
+#endif
+
+#include "secport.h"
+#include "hasht.h"
+#include "blapit.h"
+#include "hmacct.h"
+#include "secerr.h"
+
+/* MAX_HASH_BIT_COUNT_BYTES is the maximum number of bytes in the hash's length
+ * field. (SHA-384/512 have 128-bit length.) */
+#define MAX_HASH_BIT_COUNT_BYTES 16
+
+/* Some utility functions are needed:
+ *
+ * These macros return the given value with the MSB copied to all the other
+ * bits. They use the fact that an 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 on odd CPUs.
+ *
+ * Note: the argument to these macros must be an unsigned int.
+ * */
+#define DUPLICATE_MSB_TO_ALL(x) ((unsigned int)((int)(x) >> (sizeof(int) * 8 - 1)))
+#define DUPLICATE_MSB_TO_ALL_8(x) ((unsigned char)(DUPLICATE_MSB_TO_ALL(x)))
+
+/* constantTimeGE returns 0xff if a>=b and 0x00 otherwise, where a, b <
+ * MAX_UINT/2. */
+static unsigned char
+constantTimeGE(unsigned int a, unsigned int b)
+{
+ a -= b;
+ return DUPLICATE_MSB_TO_ALL(~a);
+}
+
+/* constantTimeEQ8 returns 0xff if a==b and 0x00 otherwise. */
+static unsigned char
+constantTimeEQ8(unsigned char a, unsigned char b)
+{
+ unsigned int c = a ^ b;
+ c--;
+ return DUPLICATE_MSB_TO_ALL_8(c);
+}
+
+/* MAC performs a constant time SSLv3/TLS MAC of |dataLen| bytes of |data|,
+ * where |dataLen| includes both the authenticated bytes and the MAC tag from
+ * the sender. |dataLen| must be >= the length of the MAC tag.
+ *
+ * |dataTotalLen| is >= |dataLen| and also accounts for any padding bytes
+ * that may follow the sender's MAC. (Only a single block of padding may
+ * follow in SSLv3, or up to 255 bytes in TLS.)
+ *
+ * Since the results of decryption are secret information (otherwise a
+ * padding-oracle is created), this function is constant-time with respect to
+ * |dataLen|.
+ *
+ * |header| contains either the 13-byte TLS header (containing the sequence
+ * number, record type etc), or it contains the SSLv3 header with the SSLv3
+ * padding bytes etc. */
+static SECStatus
+MAC(unsigned char *mdOut,
+ unsigned int *mdOutLen,
+ unsigned int mdOutMax,
+ const SECHashObject *hashObj,
+ const unsigned char *macSecret,
+ unsigned int macSecretLen,
+ const unsigned char *header,
+ unsigned int headerLen,
+ const unsigned char *data,
+ unsigned int dataLen,
+ unsigned int dataTotalLen,
+ unsigned char isSSLv3)
+{
+ void *mdState = hashObj->create();
+ const unsigned int mdSize = hashObj->length;
+ const unsigned int mdBlockSize = hashObj->blocklength;
+ /* mdLengthSize is the number of bytes in the length field that terminates
+ * the hash.
+ *
+ * This assumes that hash functions with a 64 byte block size use a 64-bit
+ * length, and otherwise they use a 128-bit length. This is true of {MD5,
+ * SHA*} (which are all of the hash functions specified for use with TLS
+ * today). */
+ const unsigned int mdLengthSize = mdBlockSize == 64 ? 8 : 16;
+
+ const unsigned int sslv3PadLen = hashObj->type == HASH_AlgMD5 ? 48 : 40;
+
+ /* varianceBlocks is the number of blocks of the hash that we have to
+ * calculate in constant time because they could be altered by the
+ * padding value.
+ *
+ * In SSLv3, the padding must be minimal so the end of the plaintext
+ * varies by, at most, 15+20 = 35 bytes. (We conservatively assume that
+ * the MAC size varies from 0..20 bytes.) In case the 9 bytes of hash
+ * termination (0x80 + 64-bit length) don't fit in the final block, we
+ * say that the final two blocks can vary based on the padding.
+ *
+ * TLSv1 has MACs up to 48 bytes long (SHA-384) and the padding is not
+ * required to be minimal. Therefore we say that the final six blocks
+ * can vary based on the padding.
+ *
+ * Later in the function, if the message is short and there obviously
+ * cannot be this many blocks then varianceBlocks can be reduced. */
+ unsigned int varianceBlocks = isSSLv3 ? 2 : 6;
+ /* From now on we're dealing with the MAC, which conceptually has 13
+ * bytes of `header' before the start of the data (TLS) or 71/75 bytes
+ * (SSLv3) */
+ const unsigned int len = dataTotalLen + headerLen;
+ /* maxMACBytes contains the maximum bytes of bytes in the MAC, including
+ * |header|, assuming that there's no padding. */
+ const unsigned int maxMACBytes = len - mdSize - 1;
+ /* numBlocks is the maximum number of hash blocks. */
+ const unsigned int numBlocks =
+ (maxMACBytes + 1 + mdLengthSize + mdBlockSize - 1) / mdBlockSize;
+ /* macEndOffset is the index just past the end of the data to be
+ * MACed. */
+ const unsigned int macEndOffset = dataLen + headerLen - mdSize;
+ /* c is the index of the 0x80 byte in the final hash block that
+ * contains application data. */
+ const unsigned int c = macEndOffset % mdBlockSize;
+ /* indexA is the hash block number that contains the 0x80 terminating
+ * value. */
+ const unsigned int indexA = macEndOffset / mdBlockSize;
+ /* indexB is the hash block number that contains the 64-bit hash
+ * length, in bits. */
+ const unsigned int indexB = (macEndOffset + mdLengthSize) / mdBlockSize;
+ /* bits is the hash-length in bits. It includes the additional hash
+ * block for the masked HMAC key, or whole of |header| in the case of
+ * SSLv3. */
+ unsigned int bits;
+ /* In order to calculate the MAC in constant time we have to handle
+ * the final blocks specially because the padding value could cause the
+ * end to appear somewhere in the final |varianceBlocks| blocks and we
+ * can't leak where. However, |numStartingBlocks| worth of data can
+ * be hashed right away because no padding value can affect whether
+ * they are plaintext. */
+ unsigned int numStartingBlocks = 0;
+ /* k is the starting byte offset into the conceptual header||data where
+ * we start processing. */
+ unsigned int k = 0;
+ unsigned char lengthBytes[MAX_HASH_BIT_COUNT_BYTES];
+ /* hmacPad is the masked HMAC key. */
+ unsigned char hmacPad[HASH_BLOCK_LENGTH_MAX];
+ unsigned char firstBlock[HASH_BLOCK_LENGTH_MAX];
+ unsigned char macOut[HASH_LENGTH_MAX];
+ unsigned i, j;
+
+ /* For SSLv3, if we're going to have any starting blocks then we need
+ * at least two because the header is larger than a single block. */
+ if (numBlocks > varianceBlocks + (isSSLv3 ? 1 : 0)) {
+ numStartingBlocks = numBlocks - varianceBlocks;
+ k = mdBlockSize * numStartingBlocks;
+ }
+
+ bits = 8 * macEndOffset;
+ hashObj->begin(mdState);
+ if (!isSSLv3) {
+ /* Compute the initial HMAC block. For SSLv3, the padding and
+ * secret bytes are included in |header| because they take more
+ * than a single block. */
+ bits += 8 * mdBlockSize;
+ memset(hmacPad, 0, mdBlockSize);
+ PORT_Assert(macSecretLen <= sizeof(hmacPad));
+ memcpy(hmacPad, macSecret, macSecretLen);
+ for (i = 0; i < mdBlockSize; i++)
+ hmacPad[i] ^= 0x36;
+ hashObj->update(mdState, hmacPad, mdBlockSize);
+ }
+
+ j = 0;
+ memset(lengthBytes, 0, sizeof(lengthBytes));
+ if (mdLengthSize == 16) {
+ j = 8;
+ }
+ if (hashObj->type == HASH_AlgMD5) {
+ /* MD5 appends a little-endian length. */
+ for (i = 0; i < 4; i++) {
+ lengthBytes[i + j] = bits >> (8 * i);
+ }
+ } else {
+ /* All other TLS hash functions use a big-endian length. */
+ for (i = 0; i < 4; i++) {
+ lengthBytes[4 + i + j] = bits >> (8 * (3 - i));
+ }
+ }
+
+ if (k > 0) {
+ if (isSSLv3) {
+ /* The SSLv3 header is larger than a single block.
+ * overhang is the number of bytes beyond a single
+ * block that the header consumes: either 7 bytes
+ * (SHA1) or 11 bytes (MD5). */
+ const unsigned int overhang = headerLen - mdBlockSize;
+ hashObj->update(mdState, header, mdBlockSize);
+ memcpy(firstBlock, header + mdBlockSize, overhang);
+ memcpy(firstBlock + overhang, data, mdBlockSize - overhang);
+ hashObj->update(mdState, firstBlock, mdBlockSize);
+ for (i = 1; i < k / mdBlockSize - 1; i++) {
+ hashObj->update(mdState, data + mdBlockSize * i - overhang,
+ mdBlockSize);
+ }
+ } else {
+ /* k is a multiple of mdBlockSize. */
+ memcpy(firstBlock, header, 13);
+ memcpy(firstBlock + 13, data, mdBlockSize - 13);
+ hashObj->update(mdState, firstBlock, mdBlockSize);
+ for (i = 1; i < k / mdBlockSize; i++) {
+ hashObj->update(mdState, data + mdBlockSize * i - 13,
+ mdBlockSize);
+ }
+ }
+ }
+
+ memset(macOut, 0, sizeof(macOut));
+
+ /* We now process the final hash blocks. For each block, we construct
+ * it in constant time. If i == indexA then we'll include the 0x80
+ * bytes and zero pad etc. For each block we selectively copy it, in
+ * constant time, to |macOut|. */
+ for (i = numStartingBlocks; i <= numStartingBlocks + varianceBlocks; i++) {
+ unsigned char block[HASH_BLOCK_LENGTH_MAX];
+ unsigned char isBlockA = constantTimeEQ8(i, indexA);
+ unsigned char isBlockB = constantTimeEQ8(i, indexB);
+ for (j = 0; j < mdBlockSize; j++) {
+ unsigned char isPastC = isBlockA & constantTimeGE(j, c);
+ unsigned char isPastCPlus1 = isBlockA & constantTimeGE(j, c + 1);
+ unsigned char b = 0;
+ if (k < headerLen) {
+ b = header[k];
+ } else if (k < dataTotalLen + headerLen) {
+ b = data[k - headerLen];
+ }
+ k++;
+
+ /* If this is the block containing the end of the
+ * application data, and we are at the offset for the
+ * 0x80 value, then overwrite b with 0x80. */
+ b = (b & ~isPastC) | (0x80 & isPastC);
+ /* If this the the block containing the end of the
+ * application data and we're past the 0x80 value then
+ * just write zero. */
+ b = b & ~isPastCPlus1;
+ /* If this is indexB (the final block), but not
+ * indexA (the end of the data), then the 64-bit
+ * length didn't fit into indexA and we're having to
+ * add an extra block of zeros. */
+ b &= ~isBlockB | isBlockA;
+
+ /* The final bytes of one of the blocks contains the length. */
+ if (j >= mdBlockSize - mdLengthSize) {
+ /* If this is indexB, write a length byte. */
+ b = (b & ~isBlockB) |
+ (isBlockB & lengthBytes[j - (mdBlockSize - mdLengthSize)]);
+ }
+ block[j] = b;
+ }
+
+ hashObj->update(mdState, block, mdBlockSize);
+ hashObj->end_raw(mdState, block, NULL, mdSize);
+ /* If this is indexB, copy the hash value to |macOut|. */
+ for (j = 0; j < mdSize; j++) {
+ macOut[j] |= block[j] & isBlockB;
+ }
+ }
+
+ hashObj->begin(mdState);
+
+ if (isSSLv3) {
+ /* We repurpose |hmacPad| to contain the SSLv3 pad2 block. */
+ for (i = 0; i < sslv3PadLen; i++)
+ hmacPad[i] = 0x5c;
+
+ hashObj->update(mdState, macSecret, macSecretLen);
+ hashObj->update(mdState, hmacPad, sslv3PadLen);
+ hashObj->update(mdState, macOut, mdSize);
+ } else {
+ /* Complete the HMAC in the standard manner. */
+ for (i = 0; i < mdBlockSize; i++)
+ hmacPad[i] ^= 0x6a;
+
+ hashObj->update(mdState, hmacPad, mdBlockSize);
+ hashObj->update(mdState, macOut, mdSize);
+ }
+
+ hashObj->end(mdState, mdOut, mdOutLen, mdOutMax);
+ hashObj->destroy(mdState, PR_TRUE);
+
+ return SECSuccess;
+}
+
+SECStatus
+HMAC_ConstantTime(
+ unsigned char *result,
+ unsigned int *resultLen,
+ unsigned int maxResultLen,
+ const SECHashObject *hashObj,
+ const unsigned char *secret,
+ unsigned int secretLen,
+ const unsigned char *header,
+ unsigned int headerLen,
+ const unsigned char *body,
+ unsigned int bodyLen,
+ unsigned int bodyTotalLen)
+{
+ if (hashObj->end_raw == NULL)
+ return SECFailure;
+ return MAC(result, resultLen, maxResultLen, hashObj, secret, secretLen,
+ header, headerLen, body, bodyLen, bodyTotalLen,
+ 0 /* not SSLv3 */);
+}
+
+SECStatus
+SSLv3_MAC_ConstantTime(
+ unsigned char *result,
+ unsigned int *resultLen,
+ unsigned int maxResultLen,
+ const SECHashObject *hashObj,
+ const unsigned char *secret,
+ unsigned int secretLen,
+ const unsigned char *header,
+ unsigned int headerLen,
+ const unsigned char *body,
+ unsigned int bodyLen,
+ unsigned int bodyTotalLen)
+{
+ if (hashObj->end_raw == NULL)
+ return SECFailure;
+ return MAC(result, resultLen, maxResultLen, hashObj, secret, secretLen,
+ header, headerLen, body, bodyLen, bodyTotalLen,
+ 1 /* SSLv3 */);
+}