/* tlsprf.c - TLS Pseudo Random Function (PRF) implementation
 *
 * 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/. */

#include "pkcs11i.h"
#include "blapi.h"
#include "secerr.h"

static void
sftk_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.           */
    HASH_HashType cxHashAlg;  /* hash algorithm to use for TLS 1.2+        */
    unsigned int cxOutLen;    /* bytes of output if nonzero                */
    unsigned char cxBuf[512]; /* actual size may be larger than 512.       */
} TLSPRFContext;

static void
sftk_TLSPRFHashUpdate(void *ctx, const unsigned char *data,
                      unsigned int data_len)
{
    TLSPRFContext *cx = ctx;
    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
sftk_TLSPRFEnd(void *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
sftk_TLSPRFUpdate(void *ctx,
                  unsigned char *sig,        /* output goes here. */
                  unsigned int *sigLen,      /* how much output.  */
                  unsigned int maxLen,       /* output buffer size */
                  const unsigned char *hash, /* unused. */
                  unsigned int hashLen)      /* unused. */
{
    TLSPRFContext *cx = ctx;
    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;
    if (cx->cxOutLen == 0) {
        sigItem.len = maxLen;
    } else if (cx->cxOutLen <= maxLen) {
        sigItem.len = cx->cxOutLen;
    } else {
        PORT_SetError(SEC_ERROR_OUTPUT_LEN);
        return SECFailure;
    }

    if (cx->cxHashAlg != HASH_AlgNULL) {
        rv = TLS_P_hash(cx->cxHashAlg, &secretItem, NULL, &seedItem, &sigItem,
                        cx->cxIsFIPS);
    } else {
        rv = TLS_PRF(&secretItem, NULL, &seedItem, &sigItem, cx->cxIsFIPS);
    }
    if (rv == SECSuccess && sigLen != NULL)
        *sigLen = sigItem.len;
    return rv;
}

static SECStatus
sftk_TLSPRFVerify(void *ctx,
                  const unsigned char *sig,  /* input, for comparison. */
                  unsigned int sigLen,       /* length of sig.         */
                  const unsigned char *hash, /* data to be verified.   */
                  unsigned int hashLen)      /* size of hash data.     */
{
    TLSPRFContext *cx = ctx;
    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.
        */
        sftk_TLSPRFHashUpdate(cx, hash, hashLen);
    }
    rv = sftk_TLSPRFUpdate(cx, tmp, &tmpLen, sigLen, NULL, 0);
    if (rv == SECSuccess) {
        rv = (SECStatus)(1 - !NSS_SecureMemcmp(tmp, sig, sigLen));
    }
    PORT_ZFree(tmp, sigLen);
    return rv;
}

static void
sftk_TLSPRFHashDestroy(void *ctx, PRBool freeit)
{
    TLSPRFContext *cx = ctx;
    if (freeit) {
        if (cx->cxBufPtr != cx->cxBuf)
            PORT_ZFree(cx->cxBufPtr, cx->cxBufSize);
        PORT_ZFree(cx, cx->cxSize);
    }
}

CK_RV
sftk_TLSPRFInit(SFTKSessionContext *context,
                SFTKObject *key,
                CK_KEY_TYPE key_type,
                HASH_HashType hash_alg,
                unsigned int out_len)
{
    SFTKAttribute *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 = sftk_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 - offsetof(TLSPRFContext, cxBuf);
    prf_cx->cxRv = SECSuccess;
    prf_cx->cxIsFIPS = sftk_isFIPS(key->slot->slotID);
    prf_cx->cxBufPtr = prf_cx->cxBuf;
    prf_cx->cxHashAlg = hash_alg;
    prf_cx->cxOutLen = out_len;
    if (keySize)
        PORT_Memcpy(prf_cx->cxBufPtr, keyVal->attrib.pValue, keySize);

    context->hashInfo = (void *)prf_cx;
    context->cipherInfo = (void *)prf_cx;
    context->hashUpdate = sftk_TLSPRFHashUpdate;
    context->end = sftk_TLSPRFEnd;
    context->update = sftk_TLSPRFUpdate;
    context->verify = sftk_TLSPRFVerify;
    context->destroy = sftk_TLSPRFNull;
    context->hashdestroy = sftk_TLSPRFHashDestroy;
    crv = CKR_OK;

done:
    if (keyVal)
        sftk_FreeAttribute(keyVal);
    return crv;
}
