/* tlsprf.c - TLS Pseudo Random Function (PRF) implementation * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1994-2000 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the * terms of the GNU General Public License Version 2 or later (the * "GPL"), in which case the provisions of the GPL are applicable * instead of those above. If you wish to allow use of your * version of this file only under the terms of the GPL and not to * allow others to use your version of this file under the MPL, * indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by * the GPL. If you do not delete the provisions above, a recipient * may use your version of this file under either the MPL or the * GPL. * * $Id$ */ #include "pkcs11i.h" #include "sechash.h" #include "alghmac.h" #define PK11_OFFSETOF(str, memb) ((PRPtrdiff)(&(((str *)0)->memb))) #define PHASH_STATE_MAX_LEN 20 /* TLS P_hash function */ static SECStatus pk11_P_hash(HASH_HashType hashType, const SECItem *secret, const char *label, SECItem *seed, SECItem *result, PRBool isFIPS) { unsigned char state[PHASH_STATE_MAX_LEN]; unsigned char outbuf[PHASH_STATE_MAX_LEN]; unsigned int state_len = 0, label_len = 0, outbuf_len = 0, chunk_size; unsigned int remaining; unsigned char *res; SECStatus status; HMACContext *cx; SECStatus rv = SECFailure; const SECHashObject *hashObj = &SECRawHashObjects[hashType]; PORT_Assert((secret != NULL) && (secret->data != NULL || !secret->len)); PORT_Assert((seed != NULL) && (seed->data != NULL)); PORT_Assert((result != NULL) && (result->data != NULL)); remaining = result->len; res = result->data; if (label != NULL) label_len = PORT_Strlen(label); cx = HMAC_Create(hashObj, secret->data, secret->len, isFIPS); if (cx == NULL) goto loser; /* initialize the state = A(1) = HMAC_hash(secret, seed) */ HMAC_Begin(cx); HMAC_Update(cx, (unsigned char *)label, label_len); HMAC_Update(cx, seed->data, seed->len); status = HMAC_Finish(cx, state, &state_len, PHASH_STATE_MAX_LEN); if (status != SECSuccess) goto loser; /* generate a block at a time until we're done */ while (remaining > 0) { HMAC_Begin(cx); HMAC_Update(cx, state, state_len); if (label_len) HMAC_Update(cx, (unsigned char *)label, label_len); HMAC_Update(cx, seed->data, seed->len); status = HMAC_Finish(cx, outbuf, &outbuf_len, PHASH_STATE_MAX_LEN); if (status != SECSuccess) goto loser; /* Update the state = A(i) = HMAC_hash(secret, A(i-1)) */ HMAC_Begin(cx); HMAC_Update(cx, state, state_len); status = HMAC_Finish(cx, state, &state_len, PHASH_STATE_MAX_LEN); if (status != SECSuccess) goto loser; chunk_size = PR_MIN(outbuf_len, remaining); PORT_Memcpy(res, &outbuf, chunk_size); res += chunk_size; remaining -= chunk_size; } rv = SECSuccess; loser: /* if (cx) HMAC_Destroy(cx); */ /* clear out state so it's not left on the stack */ if (cx) HMAC_Destroy(cx); PORT_Memset(state, 0, sizeof(state)); PORT_Memset(outbuf, 0, sizeof(outbuf)); return rv; } SECStatus pk11_PRF(const SECItem *secret, const char *label, SECItem *seed, SECItem *result, PRBool isFIPS) { SECStatus rv = SECFailure, status; unsigned int i; SECItem tmp = { siBuffer, NULL, 0}; SECItem S1; SECItem S2; PORT_Assert((secret != NULL) && (secret->data != NULL || !secret->len)); PORT_Assert((seed != NULL) && (seed->data != NULL)); PORT_Assert((result != NULL) && (result->data != NULL)); S1.type = siBuffer; S1.len = (secret->len / 2) + (secret->len & 1); S1.data = secret->data; S2.type = siBuffer; S2.len = S1.len; S2.data = secret->data + (secret->len - S2.len); tmp.data = (unsigned char*)PORT_Alloc(result->len); if (tmp.data == NULL) goto loser; tmp.len = result->len; status = pk11_P_hash(HASH_AlgMD5, &S1, label, seed, result, isFIPS); if (status != SECSuccess) goto loser; status = pk11_P_hash(HASH_AlgSHA1, &S2, label, seed, &tmp, isFIPS); if (status != SECSuccess) goto loser; for (i = 0; i < result->len; i++) result->data[i] ^= tmp.data[i]; rv = SECSuccess; loser: if (tmp.data != NULL) PORT_ZFree(tmp.data, tmp.len); return rv; } static void pk11_TLSPRFNull(void *data, PRBool freeit) { return; } typedef struct { PRUint32 cxSize; /* size of allocated block, in bytes. */ PRUint32 cxBufSize; /* sizeof buffer at cxBufPtr. */ unsigned char *cxBufPtr; /* points to real buffer, may be cxBuf. */ PRUint32 cxKeyLen; /* bytes of cxBufPtr containing key. */ PRUint32 cxDataLen; /* bytes of cxBufPtr containing data. */ SECStatus cxRv; /* records failure of void functions. */ PRBool cxIsFIPS; /* true if conforming to FIPS 198. */ unsigned char cxBuf[512]; /* actual size may be larger than 512. */ } TLSPRFContext; static void pk11_TLSPRFHashUpdate(TLSPRFContext *cx, const unsigned char *data, unsigned int data_len) { PRUint32 bytesUsed = cx->cxKeyLen + cx->cxDataLen; if (cx->cxRv != SECSuccess) /* function has previously failed. */ return; if (bytesUsed + data_len > cx->cxBufSize) { /* We don't use realloc here because ** (a) realloc doesn't zero out the old block, and ** (b) if realloc fails, we lose the old block. */ PRUint32 newBufSize = bytesUsed + data_len + 512; unsigned char * newBuf = (unsigned char *)PORT_Alloc(newBufSize); if (!newBuf) { cx->cxRv = SECFailure; return; } PORT_Memcpy(newBuf, cx->cxBufPtr, bytesUsed); if (cx->cxBufPtr != cx->cxBuf) { PORT_ZFree(cx->cxBufPtr, bytesUsed); } cx->cxBufPtr = newBuf; cx->cxBufSize = newBufSize; } PORT_Memcpy(cx->cxBufPtr + bytesUsed, data, data_len); cx->cxDataLen += data_len; } static void pk11_TLSPRFEnd(TLSPRFContext *ctx, unsigned char *hashout, unsigned int *pDigestLen, unsigned int maxDigestLen) { *pDigestLen = 0; /* tells Verify that no data has been input yet. */ } /* Compute the PRF values from the data previously input. */ static SECStatus pk11_TLSPRFUpdate(TLSPRFContext *cx, unsigned char *sig, /* output goes here. */ unsigned int * sigLen, /* how much output. */ unsigned int maxLen, /* output buffer size */ unsigned char *hash, /* unused. */ unsigned int hashLen) /* unused. */ { SECStatus rv; SECItem sigItem; SECItem seedItem; SECItem secretItem; if (cx->cxRv != SECSuccess) return cx->cxRv; secretItem.data = cx->cxBufPtr; secretItem.len = cx->cxKeyLen; seedItem.data = cx->cxBufPtr + cx->cxKeyLen; seedItem.len = cx->cxDataLen; sigItem.data = sig; sigItem.len = maxLen; rv = pk11_PRF(&secretItem, NULL, &seedItem, &sigItem, cx->cxIsFIPS); if (rv == SECSuccess && sigLen != NULL) *sigLen = sigItem.len; return rv; } static SECStatus pk11_TLSPRFVerify(TLSPRFContext *cx, unsigned char *sig, /* input, for comparison. */ unsigned int sigLen, /* length of sig. */ unsigned char *hash, /* data to be verified. */ unsigned int hashLen) /* size of hash data. */ { unsigned char * tmp = (unsigned char *)PORT_Alloc(sigLen); unsigned int tmpLen = sigLen; SECStatus rv; if (!tmp) return SECFailure; if (hashLen) { /* hashLen is non-zero when the user does a one-step verify. ** In this case, none of the data has been input yet. */ pk11_TLSPRFHashUpdate(cx, hash, hashLen); } rv = pk11_TLSPRFUpdate(cx, tmp, &tmpLen, sigLen, NULL, 0); if (rv == SECSuccess) { rv = (SECStatus)(1 - !PORT_Memcmp(tmp, sig, sigLen)); } PORT_ZFree(tmp, sigLen); return rv; } static void pk11_TLSPRFHashDestroy(TLSPRFContext *cx, PRBool freeit) { if (freeit) { if (cx->cxBufPtr != cx->cxBuf) PORT_ZFree(cx->cxBufPtr, cx->cxBufSize); PORT_ZFree(cx, cx->cxSize); } } CK_RV pk11_TLSPRFInit(PK11SessionContext *context, PK11Object * key, CK_KEY_TYPE key_type) { PK11Attribute * keyVal; TLSPRFContext * prf_cx; CK_RV crv = CKR_HOST_MEMORY; PRUint32 keySize; PRUint32 blockSize; if (key_type != CKK_GENERIC_SECRET) return CKR_KEY_TYPE_INCONSISTENT; /* CKR_KEY_FUNCTION_NOT_PERMITTED */ context->multi = PR_TRUE; keyVal = pk11_FindAttribute(key, CKA_VALUE); keySize = (!keyVal) ? 0 : keyVal->attrib.ulValueLen; blockSize = keySize + sizeof(TLSPRFContext); prf_cx = (TLSPRFContext *)PORT_Alloc(blockSize); if (!prf_cx) goto done; prf_cx->cxSize = blockSize; prf_cx->cxKeyLen = keySize; prf_cx->cxDataLen = 0; prf_cx->cxBufSize = blockSize - PK11_OFFSETOF(TLSPRFContext, cxBuf); prf_cx->cxRv = SECSuccess; prf_cx->cxIsFIPS = (key->slot->slotID == FIPS_SLOT_ID); prf_cx->cxBufPtr = prf_cx->cxBuf; if (keySize) PORT_Memcpy(prf_cx->cxBufPtr, keyVal->attrib.pValue, keySize); context->hashInfo = (void *) prf_cx; context->cipherInfo = (void *) prf_cx; context->hashUpdate = (PK11Hash) pk11_TLSPRFHashUpdate; context->end = (PK11End) pk11_TLSPRFEnd; context->update = (PK11Cipher) pk11_TLSPRFUpdate; context->verify = (PK11Verify) pk11_TLSPRFVerify; context->destroy = (PK11Destroy) pk11_TLSPRFNull; context->hashdestroy = (PK11Destroy) pk11_TLSPRFHashDestroy; crv = CKR_OK; done: if (keyVal) pk11_FreeAttribute(keyVal); return crv; }