INT AirPDcapCcmpDecrypt( UINT8 *m, gint mac_header_len, INT len, UCHAR TK1[16]) { PAIRPDCAP_MAC_FRAME wh; UINT8 aad[2 * AES_BLOCK_LEN]; UINT8 b0[AES_BLOCK_LEN], b[AES_BLOCK_LEN], a[AES_BLOCK_LEN]; UINT8 mic[AES_BLOCK_LEN]; size_t data_len; UINT i; UINT8 *pos; UINT space; INT z = mac_header_len; rijndael_ctx key; UINT64 PN; UINT8 *ivp=m+z; PN = READ_6(ivp[0], ivp[1], ivp[4], ivp[5], ivp[6], ivp[7]); /* freebsd */ rijndael_set_key(&key, TK1, 128); wh = (PAIRPDCAP_MAC_FRAME )m; data_len = len - (z + AIRPDCAP_CCMP_HEADER+AIRPDCAP_CCMP_TRAILER); if (data_len < 1) return 0; ccmp_init_blocks(&key, wh, PN, data_len, b0, aad, a, b); memcpy(mic, m+len-AIRPDCAP_CCMP_TRAILER, AIRPDCAP_CCMP_TRAILER); XOR_BLOCK(mic, b, AIRPDCAP_CCMP_TRAILER); i = 1; pos = (UINT8 *)m + z + AIRPDCAP_CCMP_HEADER; space = len - (z + AIRPDCAP_CCMP_HEADER); if (space > data_len) space = (UINT)data_len; while (space >= AES_BLOCK_LEN) { CCMP_DECRYPT(i, b, b0, pos, a, AES_BLOCK_LEN); pos += AES_BLOCK_LEN, space -= AES_BLOCK_LEN; data_len -= AES_BLOCK_LEN; i++; } if (space != 0) /* short last block */ CCMP_DECRYPT(i, b, b0, pos, a, space); /* MIC Key ?= MIC */ if (memcmp(mic, a, AIRPDCAP_CCMP_TRAILER) == 0) { return 0; } /* TODO replay check (IEEE 802.11i-2004, pg. 62) */ /* TODO PN must be incremental (IEEE 802.11i-2004, pg. 62) */ return 1; }
static void ccmp_init_blocks( rijndael_ctx *ctx, PAIRPDCAP_MAC_FRAME wh, UINT64 pn, size_t dlen, UINT8 b0[AES_BLOCK_LEN], UINT8 aad[2 * AES_BLOCK_LEN], UINT8 a[AES_BLOCK_LEN], UINT8 b[AES_BLOCK_LEN]) { #define IS_4ADDRESS(wh) \ ((wh->fc[1] & AIRPDCAP_FC1_DIR_MASK) == AIRPDCAP_FC1_DIR_DSTODS) #define IS_QOS_DATA(wh) AIRPDCAP_QOS_HAS_SEQ(wh) memset(aad, 0, 2*AES_BLOCK_LEN); /* CCM Initial Block: * Flag (Include authentication header, M=3 (8-octet MIC), * L=1 (2-octet Dlen)) * Nonce: 0x00 | A2 | PN * Dlen */ b0[0] = 0x59; /* NB: b0[1] set below */ AIRPDCAP_ADDR_COPY(b0 + 2, wh->addr2); b0[8] = (UINT8)(pn >> 40); b0[9] = (UINT8)(pn >> 32); b0[10] = (UINT8)(pn >> 24); b0[11] = (UINT8)(pn >> 16); b0[12] = (UINT8)(pn >> 8); b0[13] = (UINT8)(pn >> 0); b0[14] = (UINT8)((UINT8)(dlen >> 8) & 0xff); b0[15] = (UINT8)(dlen & 0xff); /* AAD: * FC with bits 4..6 and 11..13 masked to zero; 14 is always one * A1 | A2 | A3 * SC with bits 4..15 (seq#) masked to zero * A4 (if present) * QC (if present) */ aad[0] = 0; /* AAD length >> 8 */ /* NB: aad[1] set below */ aad[2] = (UINT8)(wh->fc[0] & 0x8f); /* XXX magic #s */ aad[3] = (UINT8)(wh->fc[1] & 0xc7); /* XXX magic #s */ /* NB: we know 3 addresses are contiguous */ memcpy(aad + 4, &wh->addr1[0], 3 * AIRPDCAP_MAC_LEN); aad[22] = (UINT8)(wh->seq[0] & AIRPDCAP_SEQ_FRAG_MASK); aad[23] = 0; /* all bits masked */ /* * Construct variable-length portion of AAD based * on whether this is a 4-address frame/QOS frame. * We always zero-pad to 32 bytes before running it * through the cipher. * * We also fill in the priority bits of the CCM * initial block as we know whether or not we have * a QOS frame. */ if (IS_4ADDRESS(wh)) { AIRPDCAP_ADDR_COPY(aad + 24, ((PAIRPDCAP_MAC_FRAME_ADDR4)wh)->addr4); if (IS_QOS_DATA(wh)) { PAIRPDCAP_MAC_FRAME_ADDR4_QOS qwh4 = (PAIRPDCAP_MAC_FRAME_ADDR4_QOS) wh; aad[30] = (UINT8)(qwh4->qos[0] & 0x0f);/* just priority bits */ aad[31] = 0; b0[1] = aad[30]; aad[1] = 22 + AIRPDCAP_MAC_LEN + 2; } else { memset(&aad[30], 0, 2); b0[1] = 0; aad[1] = 22 + AIRPDCAP_MAC_LEN; } } else { if (IS_QOS_DATA(wh)) { PAIRPDCAP_MAC_FRAME_QOS qwh = (PAIRPDCAP_MAC_FRAME_QOS) wh; aad[24] = (UINT8)(qwh->qos[0] & 0x0f); /* just priority bits */ aad[25] = 0; b0[1] = aad[24]; aad[1] = 22 + 2; } else { memset(&aad[24], 0, 2); b0[1] = 0; aad[1] = 22; } memset(&aad[26], 0, 4); } /* Start with the first block and AAD */ rijndael_encrypt(ctx, b0, a); XOR_BLOCK(a, aad, AES_BLOCK_LEN); rijndael_encrypt(ctx, a, a); XOR_BLOCK(a, &aad[AES_BLOCK_LEN], AES_BLOCK_LEN); rijndael_encrypt(ctx, a, a); b0[0] &= 0x07; b0[14] = b0[15] = 0; rijndael_encrypt(ctx, b0, b); /** //XOR( m + len - 8, b, 8 ); **/ #undef IS_QOS_DATA #undef IS_4ADDRESS }
/* * See addemdum to NIST SP 800-38A * Decrypt, Expect CS-1: input. See the comment on the encrypt side * to understand what CS-2 and CS-3 mean. * * To convert the input buffer to CS-1 from ... * CS-2 (Schneier): do * unsigned char tmp[MAX_BLOCK_SIZE]; * pad = inlen % blocksize; * if (pad) { * memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize); * memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad); * memcpy(inbuf+inlen-blocksize, tmp, blocksize); * } * CS-3 (Kerberos): do * unsigned char tmp[MAX_BLOCK_SIZE]; * pad = inlen % blocksize; * if (pad == 0) { * pad = blocksize; * } * memcpy(tmp, inbuf+inlen-blocksize-pad, blocksize); * memcpy(inbuf+inlen-blocksize-pad,inbuf+inlen-pad, pad); * memcpy(inbuf+inlen-blocksize, tmp, blocksize); */ SECStatus CTS_DecryptUpdate(CTSContext *cts, unsigned char *outbuf, unsigned int *outlen, unsigned int maxout, const unsigned char *inbuf, unsigned int inlen, unsigned int blocksize) { unsigned char *Pn; unsigned char Cn_2[MAX_BLOCK_SIZE]; /* block Cn-2 */ unsigned char Cn_1[MAX_BLOCK_SIZE]; /* block Cn-1 */ unsigned char Cn[MAX_BLOCK_SIZE]; /* block Cn */ unsigned char lastBlock[MAX_BLOCK_SIZE]; const unsigned char *tmp; unsigned char *saveout = outbuf; unsigned int tmpLen; unsigned int fullblocks, pad; unsigned int i; SECStatus rv; if (inlen < blocksize) { PORT_SetError(SEC_ERROR_INPUT_LEN); return SECFailure; } if (maxout < inlen) { *outlen = inlen; PORT_SetError(SEC_ERROR_OUTPUT_LEN); return SECFailure; } fullblocks = (inlen / blocksize) * blocksize; /* even though we expect the input to be CS-1, CS-2 is easier to parse, * so convert to CS-2 immediately. NOTE: this is the same code as in * the comment for encrypt. NOTE2: since we can't modify inbuf unless * inbuf and outbuf overlap, just copy inbuf to outbuf and modify it there */ pad = inlen - fullblocks; if (pad != 0) { if (inbuf != outbuf) { memcpy(outbuf, inbuf, inlen); /* keep the names so we logically know how we are using the * buffers */ inbuf = outbuf; } memcpy(lastBlock, inbuf + inlen - blocksize, blocksize); /* we know inbuf == outbuf now, inbuf is declared const and can't * be the target, so use outbuf for the target here */ memcpy(outbuf + inlen - pad, inbuf + inlen - blocksize - pad, pad); memcpy(outbuf + inlen - blocksize - pad, lastBlock, blocksize); } /* save the previous to last block so we can undo the misordered * chaining */ tmp = (fullblocks < blocksize * 2) ? cts->iv : inbuf + fullblocks - blocksize * 2; PORT_Memcpy(Cn_2, tmp, blocksize); PORT_Memcpy(Cn, inbuf + fullblocks - blocksize, blocksize); rv = (*cts->cipher)(cts->context, outbuf, outlen, maxout, inbuf, fullblocks, blocksize); if (rv != SECSuccess) { return SECFailure; } *outlen = fullblocks; /* AES low level doesn't set outlen */ inbuf += fullblocks; inlen -= fullblocks; if (inlen == 0) { return SECSuccess; } outbuf += fullblocks; /* recover the stolen text */ PORT_Memset(lastBlock, 0, blocksize); PORT_Memcpy(lastBlock, inbuf, inlen); PORT_Memcpy(Cn_1, inbuf, inlen); Pn = outbuf - blocksize; /* inbuf points to Cn-1* in the input buffer */ /* NOTE: below there are 2 sections marked "make up for the out of order * cbc decryption". You may ask, what is going on here. * Short answer: CBC automatically xors the plain text with the previous * encrypted block. We are decrypting the last 2 blocks out of order, so * we have to 'back out' the decrypt xor and 'add back' the encrypt xor. * Long answer: When we encrypted, we encrypted as follows: * Pn-2, Pn-1, (Pn || 0), but on decryption we can't * decrypt Cn-1 until we decrypt Cn because part of Cn-1 is stored in * Cn (see below). So above we decrypted all the full blocks: * Cn-2, Cn, * to get: * Pn-2, Pn, Except that Pn is not yet corect. On encrypt, we * xor'd Pn || 0 with Cn-1, but on decrypt we xor'd it with Cn-2 * To recover Pn, we xor the block with Cn-1* || 0 (in last block) and * Cn-2 to get Pn || Cn-1**. Pn can then be written to the output buffer * and we can now reunite Cn-1. With the full Cn-1 we can decrypt it, * but now decrypt is going to xor the decrypted data with Cn instead of * Cn-2. xoring Cn and Cn-2 restores the original Pn-1 and we can now * write that oout to the buffer */ /* make up for the out of order CBC decryption */ XOR_BLOCK(lastBlock, Cn_2, blocksize); XOR_BLOCK(lastBlock, Pn, blocksize); /* last buf now has Pn || Cn-1**, copy out Pn */ PORT_Memcpy(outbuf, lastBlock, inlen); *outlen += inlen; /* copy Cn-1* into last buf to recover Cn-1 */ PORT_Memcpy(lastBlock, Cn_1, inlen); /* note: because Cn and Cn-1 were out of order, our pointer to Pn also * points to where Pn-1 needs to reside. From here on out read Pn in * the code as really Pn-1. */ rv = (*cts->cipher)(cts->context, Pn, &tmpLen, blocksize, lastBlock, blocksize, blocksize); if (rv != SECSuccess) { PORT_Memset(lastBlock, 0, blocksize); PORT_Memset(saveout, 0, *outlen); return SECFailure; } /* make up for the out of order CBC decryption */ XOR_BLOCK(Pn, Cn_2, blocksize); XOR_BLOCK(Pn, Cn, blocksize); /* reset iv to Cn */ PORT_Memcpy(cts->iv, Cn, blocksize); /* This makes Cn the last block for the next decrypt operation, which * matches the encrypt. We don't care about the contexts of last block, * only the side effect of setting the internal IV */ (void)(*cts->cipher)(cts->context, lastBlock, &tmpLen, blocksize, Cn, blocksize, blocksize); /* clear last block. At this point last block contains Pn xor Cn_1 xor * Cn_2, both of with an attacker would know, so we need to clear this * buffer out */ PORT_Memset(lastBlock, 0, blocksize); /* Cn, Cn_1, and Cn_2 have encrypted data, so no need to clear them */ return SECSuccess; }