/* 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 "ckmk.h"

/* Sigh, For all the talk about 'ease of use', apple has hidden the interfaces
 * needed to be able to truly use CSSM. These came from their modification
 * to NSS's S/MIME code. The following two functions currently are not
 * part of the SecKey.h interface.
 */
OSStatus
SecKeyGetCredentials(
    SecKeyRef keyRef,
    CSSM_ACL_AUTHORIZATION_TAG authTag,
    int type,
    const CSSM_ACCESS_CREDENTIALS **creds);

/* this function could be implemented using 'SecKeychainItemCopyKeychain' and
 * 'SecKeychainGetCSPHandle' */
OSStatus
SecKeyGetCSPHandle(
    SecKeyRef keyRef,
    CSSM_CSP_HANDLE *cspHandle);

typedef struct ckmkInternalCryptoOperationRSAPrivStr
    ckmkInternalCryptoOperationRSAPriv;
struct ckmkInternalCryptoOperationRSAPrivStr {
    NSSCKMDCryptoOperation mdOperation;
    NSSCKMDMechanism *mdMechanism;
    ckmkInternalObject *iKey;
    NSSItem *buffer;
    CSSM_CC_HANDLE cssmContext;
};

typedef enum {
    CKMK_DECRYPT,
    CKMK_SIGN
} ckmkRSAOpType;

/*
 * ckmk_mdCryptoOperationRSAPriv_Create
 */
static NSSCKMDCryptoOperation *
ckmk_mdCryptoOperationRSAPriv_Create(
    const NSSCKMDCryptoOperation *proto,
    NSSCKMDMechanism *mdMechanism,
    NSSCKMDObject *mdKey,
    ckmkRSAOpType type,
    CK_RV *pError)
{
    ckmkInternalObject *iKey = (ckmkInternalObject *)mdKey->etc;
    const NSSItem *classItem = nss_ckmk_FetchAttribute(iKey, CKA_CLASS, pError);
    const NSSItem *keyType = nss_ckmk_FetchAttribute(iKey, CKA_KEY_TYPE, pError);
    ckmkInternalCryptoOperationRSAPriv *iOperation;
    SecKeyRef privateKey;
    OSStatus macErr;
    CSSM_RETURN cssmErr;
    const CSSM_KEY *cssmKey;
    CSSM_CSP_HANDLE cspHandle;
    const CSSM_ACCESS_CREDENTIALS *creds = NULL;
    CSSM_CC_HANDLE cssmContext;
    CSSM_ACL_AUTHORIZATION_TAG authType;

    /* make sure we have the right objects */
    if (((const NSSItem *)NULL == classItem) ||
        (sizeof(CK_OBJECT_CLASS) != classItem->size) ||
        (CKO_PRIVATE_KEY != *(CK_OBJECT_CLASS *)classItem->data) ||
        ((const NSSItem *)NULL == keyType) ||
        (sizeof(CK_KEY_TYPE) != keyType->size) ||
        (CKK_RSA != *(CK_KEY_TYPE *)keyType->data)) {
        *pError = CKR_KEY_TYPE_INCONSISTENT;
        return (NSSCKMDCryptoOperation *)NULL;
    }

    privateKey = (SecKeyRef)iKey->u.item.itemRef;
    macErr = SecKeyGetCSSMKey(privateKey, &cssmKey);
    if (noErr != macErr) {
        CKMK_MACERR("Getting CSSM Key", macErr);
        *pError = CKR_KEY_HANDLE_INVALID;
        return (NSSCKMDCryptoOperation *)NULL;
    }
    macErr = SecKeyGetCSPHandle(privateKey, &cspHandle);
    if (noErr != macErr) {
        CKMK_MACERR("Getting CSP for Key", macErr);
        *pError = CKR_KEY_HANDLE_INVALID;
        return (NSSCKMDCryptoOperation *)NULL;
    }
    switch (type) {
        case CKMK_DECRYPT:
            authType = CSSM_ACL_AUTHORIZATION_DECRYPT;
            break;
        case CKMK_SIGN:
            authType = CSSM_ACL_AUTHORIZATION_SIGN;
            break;
        default:
            *pError = CKR_GENERAL_ERROR;
#ifdef DEBUG
            fprintf(stderr, "RSAPriv_Create: bad type = %d\n", type);
#endif
            return (NSSCKMDCryptoOperation *)NULL;
    }

    macErr = SecKeyGetCredentials(privateKey, authType, 0, &creds);
    if (noErr != macErr) {
        CKMK_MACERR("Getting Credentials for Key", macErr);
        *pError = CKR_KEY_HANDLE_INVALID;
        return (NSSCKMDCryptoOperation *)NULL;
    }

    switch (type) {
        case CKMK_DECRYPT:
            cssmErr = CSSM_CSP_CreateAsymmetricContext(cspHandle, CSSM_ALGID_RSA,
                                                       creds, cssmKey, CSSM_PADDING_PKCS1, &cssmContext);
            break;
        case CKMK_SIGN:
            cssmErr = CSSM_CSP_CreateSignatureContext(cspHandle, CSSM_ALGID_RSA,
                                                      creds, cssmKey, &cssmContext);
            break;
        default:
            *pError = CKR_GENERAL_ERROR;
#ifdef DEBUG
            fprintf(stderr, "RSAPriv_Create: bad type = %d\n", type);
#endif
            return (NSSCKMDCryptoOperation *)NULL;
    }
    if (noErr != cssmErr) {
        CKMK_MACERR("Getting Context for Key", cssmErr);
        *pError = CKR_GENERAL_ERROR;
        return (NSSCKMDCryptoOperation *)NULL;
    }

    iOperation = nss_ZNEW(NULL, ckmkInternalCryptoOperationRSAPriv);
    if ((ckmkInternalCryptoOperationRSAPriv *)NULL == iOperation) {
        *pError = CKR_HOST_MEMORY;
        return (NSSCKMDCryptoOperation *)NULL;
    }
    iOperation->mdMechanism = mdMechanism;
    iOperation->iKey = iKey;
    iOperation->cssmContext = cssmContext;

    nsslibc_memcpy(&iOperation->mdOperation,
                   proto, sizeof(NSSCKMDCryptoOperation));
    iOperation->mdOperation.etc = iOperation;

    return &iOperation->mdOperation;
}

static void
ckmk_mdCryptoOperationRSAPriv_Destroy(
    NSSCKMDCryptoOperation *mdOperation,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance)
{
    ckmkInternalCryptoOperationRSAPriv *iOperation =
        (ckmkInternalCryptoOperationRSAPriv *)mdOperation->etc;

    if (iOperation->buffer) {
        nssItem_Destroy(iOperation->buffer);
    }
    if (iOperation->cssmContext) {
        CSSM_DeleteContext(iOperation->cssmContext);
    }
    nss_ZFreeIf(iOperation);
    return;
}

static CK_ULONG
ckmk_mdCryptoOperationRSA_GetFinalLength(
    NSSCKMDCryptoOperation *mdOperation,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_RV *pError)
{
    ckmkInternalCryptoOperationRSAPriv *iOperation =
        (ckmkInternalCryptoOperationRSAPriv *)mdOperation->etc;
    const NSSItem *modulus =
        nss_ckmk_FetchAttribute(iOperation->iKey, CKA_MODULUS, pError);

    return modulus->size;
}

/*
 * ckmk_mdCryptoOperationRSADecrypt_GetOperationLength
 * we won't know the length until we actually decrypt the
 * input block. Since we go to all the work to decrypt the
 * the block, we'll save if for when the block is asked for
 */
static CK_ULONG
ckmk_mdCryptoOperationRSADecrypt_GetOperationLength(
    NSSCKMDCryptoOperation *mdOperation,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    const NSSItem *input,
    CK_RV *pError)
{
    ckmkInternalCryptoOperationRSAPriv *iOperation =
        (ckmkInternalCryptoOperationRSAPriv *)mdOperation->etc;
    CSSM_DATA cssmInput;
    CSSM_DATA cssmOutput = { 0, NULL };
    PRUint32 bytesDecrypted;
    CSSM_DATA remainder = { 0, NULL };
    NSSItem output;
    CSSM_RETURN cssmErr;

    if (iOperation->buffer) {
        return iOperation->buffer->size;
    }

    cssmInput.Data = input->data;
    cssmInput.Length = input->size;

    cssmErr = CSSM_DecryptData(iOperation->cssmContext,
                               &cssmInput, 1, &cssmOutput, 1,
                               &bytesDecrypted, &remainder);
    if (CSSM_OK != cssmErr) {
        CKMK_MACERR("Decrypt Failed", cssmErr);
        *pError = CKR_DATA_INVALID;
        return 0;
    }
    /* we didn't suppy any buffers, so it should all be in remainder */
    output.data = nss_ZNEWARRAY(NULL, char, bytesDecrypted + remainder.Length);
    if (NULL == output.data) {
        free(cssmOutput.Data);
        free(remainder.Data);
        *pError = CKR_HOST_MEMORY;
        return 0;
    }
    output.size = bytesDecrypted + remainder.Length;

    if (0 != bytesDecrypted) {
        nsslibc_memcpy(output.data, cssmOutput.Data, bytesDecrypted);
        free(cssmOutput.Data);
    }
    if (0 != remainder.Length) {
        nsslibc_memcpy(((char *)output.data) + bytesDecrypted,
                       remainder.Data, remainder.Length);
        free(remainder.Data);
    }

    iOperation->buffer = nssItem_Duplicate(&output, NULL, NULL);
    nss_ZFreeIf(output.data);
    if ((NSSItem *)NULL == iOperation->buffer) {
        *pError = CKR_HOST_MEMORY;
        return 0;
    }

    return iOperation->buffer->size;
}

/*
 * ckmk_mdCryptoOperationRSADecrypt_UpdateFinal
 *
 * NOTE: ckmk_mdCryptoOperationRSADecrypt_GetOperationLength is presumed to
 * have been called previously.
 */
static CK_RV
ckmk_mdCryptoOperationRSADecrypt_UpdateFinal(
    NSSCKMDCryptoOperation *mdOperation,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    const NSSItem *input,
    NSSItem *output)
{
    ckmkInternalCryptoOperationRSAPriv *iOperation =
        (ckmkInternalCryptoOperationRSAPriv *)mdOperation->etc;
    NSSItem *buffer = iOperation->buffer;

    if ((NSSItem *)NULL == buffer) {
        return CKR_GENERAL_ERROR;
    }
    nsslibc_memcpy(output->data, buffer->data, buffer->size);
    output->size = buffer->size;
    return CKR_OK;
}

/*
 * ckmk_mdCryptoOperationRSASign_UpdateFinal
 *
 */
static CK_RV
ckmk_mdCryptoOperationRSASign_UpdateFinal(
    NSSCKMDCryptoOperation *mdOperation,
    NSSCKFWCryptoOperation *fwOperation,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    const NSSItem *input,
    NSSItem *output)
{
    ckmkInternalCryptoOperationRSAPriv *iOperation =
        (ckmkInternalCryptoOperationRSAPriv *)mdOperation->etc;
    CSSM_DATA cssmInput;
    CSSM_DATA cssmOutput = { 0, NULL };
    CSSM_RETURN cssmErr;

    cssmInput.Data = input->data;
    cssmInput.Length = input->size;

    cssmErr = CSSM_SignData(iOperation->cssmContext, &cssmInput, 1,
                            CSSM_ALGID_NONE, &cssmOutput);
    if (CSSM_OK != cssmErr) {
        CKMK_MACERR("Signed Failed", cssmErr);
        return CKR_FUNCTION_FAILED;
    }
    if (cssmOutput.Length > output->size) {
        free(cssmOutput.Data);
        return CKR_BUFFER_TOO_SMALL;
    }
    nsslibc_memcpy(output->data, cssmOutput.Data, cssmOutput.Length);
    free(cssmOutput.Data);
    output->size = cssmOutput.Length;

    return CKR_OK;
}

NSS_IMPLEMENT_DATA const NSSCKMDCryptoOperation
    ckmk_mdCryptoOperationRSADecrypt_proto = {
        NULL, /* etc */
        ckmk_mdCryptoOperationRSAPriv_Destroy,
        NULL, /* GetFinalLengh - not needed for one shot Decrypt/Encrypt */
        ckmk_mdCryptoOperationRSADecrypt_GetOperationLength,
        NULL, /* Final - not needed for one shot operation */
        NULL, /* Update - not needed for one shot operation */
        NULL, /* DigetUpdate - not needed for one shot operation */
        ckmk_mdCryptoOperationRSADecrypt_UpdateFinal,
        NULL,        /* UpdateCombo - not needed for one shot operation */
        NULL,        /* DigetKey - not needed for one shot operation */
        (void *)NULL /* null terminator */
    };

NSS_IMPLEMENT_DATA const NSSCKMDCryptoOperation
    ckmk_mdCryptoOperationRSASign_proto = {
        NULL, /* etc */
        ckmk_mdCryptoOperationRSAPriv_Destroy,
        ckmk_mdCryptoOperationRSA_GetFinalLength,
        NULL, /* GetOperationLengh - not needed for one shot Sign/Verify */
        NULL, /* Final - not needed for one shot operation */
        NULL, /* Update - not needed for one shot operation */
        NULL, /* DigetUpdate - not needed for one shot operation */
        ckmk_mdCryptoOperationRSASign_UpdateFinal,
        NULL,        /* UpdateCombo - not needed for one shot operation */
        NULL,        /* DigetKey - not needed for one shot operation */
        (void *)NULL /* null terminator */
    };

/********** NSSCKMDMechansim functions ***********************/
/*
 * ckmk_mdMechanismRSA_Destroy
 */
static void
ckmk_mdMechanismRSA_Destroy(
    NSSCKMDMechanism *mdMechanism,
    NSSCKFWMechanism *fwMechanism,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance)
{
    nss_ZFreeIf(fwMechanism);
}

/*
 * ckmk_mdMechanismRSA_GetMinKeySize
 */
static CK_ULONG
ckmk_mdMechanismRSA_GetMinKeySize(
    NSSCKMDMechanism *mdMechanism,
    NSSCKFWMechanism *fwMechanism,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_RV *pError)
{
    return 384;
}

/*
 * ckmk_mdMechanismRSA_GetMaxKeySize
 */
static CK_ULONG
ckmk_mdMechanismRSA_GetMaxKeySize(
    NSSCKMDMechanism *mdMechanism,
    NSSCKFWMechanism *fwMechanism,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_RV *pError)
{
    return 16384;
}

/*
 * ckmk_mdMechanismRSA_DecryptInit
 */
static NSSCKMDCryptoOperation *
ckmk_mdMechanismRSA_DecryptInit(
    NSSCKMDMechanism *mdMechanism,
    NSSCKFWMechanism *fwMechanism,
    CK_MECHANISM *pMechanism,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    NSSCKMDObject *mdKey,
    NSSCKFWObject *fwKey,
    CK_RV *pError)
{
    return ckmk_mdCryptoOperationRSAPriv_Create(
        &ckmk_mdCryptoOperationRSADecrypt_proto,
        mdMechanism, mdKey, CKMK_DECRYPT, pError);
}

/*
 * ckmk_mdMechanismRSA_SignInit
 */
static NSSCKMDCryptoOperation *
ckmk_mdMechanismRSA_SignInit(
    NSSCKMDMechanism *mdMechanism,
    NSSCKFWMechanism *fwMechanism,
    CK_MECHANISM *pMechanism,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    NSSCKMDObject *mdKey,
    NSSCKFWObject *fwKey,
    CK_RV *pError)
{
    return ckmk_mdCryptoOperationRSAPriv_Create(
        &ckmk_mdCryptoOperationRSASign_proto,
        mdMechanism, mdKey, CKMK_SIGN, pError);
}

NSS_IMPLEMENT_DATA const NSSCKMDMechanism
    nss_ckmk_mdMechanismRSA = {
        (void *)NULL, /* etc */
        ckmk_mdMechanismRSA_Destroy,
        ckmk_mdMechanismRSA_GetMinKeySize,
        ckmk_mdMechanismRSA_GetMaxKeySize,
        NULL, /* GetInHardware - default false */
        NULL, /* EncryptInit - default errs */
        ckmk_mdMechanismRSA_DecryptInit,
        NULL, /* DigestInit - default errs*/
        ckmk_mdMechanismRSA_SignInit,
        NULL,                         /* VerifyInit - default errs */
        ckmk_mdMechanismRSA_SignInit, /* SignRecoverInit */
        NULL,                         /* VerifyRecoverInit - default errs */
        NULL,                         /* GenerateKey - default errs */
        NULL,                         /* GenerateKeyPair - default errs */
        NULL,                         /* GetWrapKeyLength - default errs */
        NULL,                         /* WrapKey - default errs */
        NULL,                         /* UnwrapKey - default errs */
        NULL,                         /* DeriveKey - default errs */
        (void *)NULL                  /* null terminator */
    };
