/*
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * Digests transforms implementation for MSCng
 *
 * This is free software; see Copyright file in the source
 * distribution for preciese wording.
 *
 * Copyright (C) 2018 Miklos Vajna. All Rights Reserved.
 */
/**
 * SECTION:crypto
 */

#include "globals.h"

#include <string.h>

#include <xmlsec/xmlsec.h>
#include <xmlsec/keys.h>
#include <xmlsec/transforms.h>
#include <xmlsec/errors.h>
#include <xmlsec/strings.h>

#include <xmlsec/mscng/crypto.h>

#include "../cast_helpers.h"

typedef struct _xmlSecMSCngDigestCtx xmlSecMSCngDigestCtx, *xmlSecMSCngDigestCtxPtr;
struct _xmlSecMSCngDigestCtx {
    LPCWSTR pszAlgId;
    DWORD cbHash;
    PBYTE pbHash;
    BCRYPT_ALG_HANDLE hAlg;
    PBYTE pbHashObject;
    BCRYPT_HASH_HANDLE hHash;
};

/******************************************************************************
 *
 * MSCng Digest transforms
 *
 *****************************************************************************/
XMLSEC_TRANSFORM_DECLARE(MSCngDigest, xmlSecMSCngDigestCtx)
#define xmlSecMSCngDigestSize XMLSEC_TRANSFORM_SIZE(MSCngDigest)

static int      xmlSecMSCngDigestInitialize  (xmlSecTransformPtr transform);
static void     xmlSecMSCngDigestFinalize    (xmlSecTransformPtr transform);
static int      xmlSecMSCngDigestVerify      (xmlSecTransformPtr transform,
                                              const xmlSecByte* data,
                                              xmlSecSize dataSize,
                                              xmlSecTransformCtxPtr transformCtx);
static int      xmlSecMSCngDigestExecute     (xmlSecTransformPtr transform,
                                              int last,
                                              xmlSecTransformCtxPtr transformCtx);
static int      xmlSecMSCngDigestCheckId     (xmlSecTransformPtr transform);


static int
xmlSecMSCngDigestCheckId(xmlSecTransformPtr transform) {

#ifndef XMLSEC_NO_MD5
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformMd5Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_MD5 */
#ifndef XMLSEC_NO_SHA1
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha1Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_SHA1 */
#ifndef XMLSEC_NO_SHA256
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha256Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_SHA256 */
#ifndef XMLSEC_NO_SHA384
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha384Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_SHA384 */
#ifndef XMLSEC_NO_SHA512
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha512Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_SHA512 */

    return(0);
}

static int
xmlSecMSCngDigestInitialize(xmlSecTransformPtr transform) {
    xmlSecMSCngDigestCtxPtr ctx;

    xmlSecAssert2(xmlSecMSCngDigestCheckId(transform), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecMSCngDigestSize), -1);

    ctx = xmlSecMSCngDigestGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    /* initialize context */
    memset(ctx, 0, sizeof(xmlSecMSCngDigestCtx));

#ifndef XMLSEC_NO_MD5
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformMd5Id)) {
        ctx->pszAlgId = BCRYPT_MD5_ALGORITHM;
    } else
#endif /* XMLSEC_NO_MD5 */

#ifndef XMLSEC_NO_SHA1
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha1Id)) {
        ctx->pszAlgId = BCRYPT_SHA1_ALGORITHM;
    } else
#endif /* XMLSEC_NO_SHA1 */

#ifndef XMLSEC_NO_SHA256
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha256Id)) {
        ctx->pszAlgId = BCRYPT_SHA256_ALGORITHM;
    } else
#endif /* XMLSEC_NO_SHA256 */

#ifndef XMLSEC_NO_SHA384
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha384Id)) {
        ctx->pszAlgId = BCRYPT_SHA384_ALGORITHM;
    } else
#endif /* XMLSEC_NO_SHA384 */

#ifndef XMLSEC_NO_SHA512
    if(xmlSecTransformCheckId(transform, xmlSecMSCngTransformSha512Id)) {
        ctx->pszAlgId = BCRYPT_SHA512_ALGORITHM;
    } else
#endif /* XMLSEC_NO_SHA512 */

    {
        xmlSecInvalidTransfromError(transform);
        return(-1);
    }

    return(0);
}

static void xmlSecMSCngDigestFinalize(xmlSecTransformPtr transform) {
    xmlSecMSCngDigestCtxPtr ctx;

    xmlSecAssert(xmlSecMSCngDigestCheckId(transform));
    xmlSecAssert(xmlSecTransformCheckSize(transform, xmlSecMSCngDigestSize));

    ctx = xmlSecMSCngDigestGetCtx(transform);
    xmlSecAssert(ctx != NULL);

    if(ctx->hAlg != 0) {
        BCryptCloseAlgorithmProvider(ctx->hAlg, 0);
    }

    if(ctx->hHash != 0) {
        BCryptDestroyHash(ctx->hHash);
    }

    if(ctx->pbHashObject != NULL) {
        xmlFree(ctx->pbHashObject);
    }

    if(ctx->pbHash != NULL) {
        xmlFree(ctx->pbHash);
    }

    memset(ctx, 0, sizeof(xmlSecMSCngDigestCtx));
}

static int
xmlSecMSCngDigestVerify(xmlSecTransformPtr transform,
                        const xmlSecByte* data,
                        xmlSecSize dataSize,
                        xmlSecTransformCtxPtr transformCtx) {
    xmlSecMSCngDigestCtxPtr ctx;
    xmlSecSize hashSize;

    xmlSecAssert2(xmlSecMSCngDigestCheckId(transform), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecMSCngDigestSize), -1);
    xmlSecAssert2(transform->operation == xmlSecTransformOperationVerify, -1);
    xmlSecAssert2(transform->status == xmlSecTransformStatusFinished, -1);
    xmlSecAssert2(data != NULL, -1);
    xmlSecAssert2(transformCtx != NULL, -1);

    ctx = xmlSecMSCngDigestGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(ctx->cbHash > 0, -1);

    XMLSEC_SAFE_CAST_ULONG_TO_SIZE(ctx->cbHash, hashSize, return(-1), xmlSecTransformGetName(transform));
    if(dataSize != hashSize) {
        xmlSecInvalidSizeError("Digest", dataSize, hashSize,
           xmlSecTransformGetName(transform));
        transform->status = xmlSecTransformStatusFail;
        return(0);
    }

    if(memcmp(ctx->pbHash, data, hashSize) != 0) {
        xmlSecInvalidDataError("data and digest do not match",
            xmlSecTransformGetName(transform));
        transform->status = xmlSecTransformStatusFail;
        return(0);
    }

    transform->status = xmlSecTransformStatusOk;
    return(0);
}

static int
xmlSecMSCngDigestExecute(xmlSecTransformPtr transform,
                         int last,
                         xmlSecTransformCtxPtr transformCtx) {
    xmlSecMSCngDigestCtxPtr ctx;
    xmlSecBufferPtr in, out;
    NTSTATUS status;
    int ret;
    DWORD cbData = 0;
    DWORD cbHashObject = 0;

    xmlSecAssert2(xmlSecMSCngDigestCheckId(transform), -1);
    xmlSecAssert2((transform->operation == xmlSecTransformOperationSign) || (transform->operation == xmlSecTransformOperationVerify), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecMSCngDigestSize), -1);
    xmlSecAssert2(transformCtx != NULL, -1);

    in = &(transform->inBuf);
    xmlSecAssert2(in != NULL, -1);

    out = &(transform->outBuf);
    xmlSecAssert2(out != NULL, -1);

    ctx = xmlSecMSCngDigestGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);

    if(transform->status == xmlSecTransformStatusNone) {
        /* open an algorithm handle */
        status = BCryptOpenAlgorithmProvider(
            &ctx->hAlg,
            ctx->pszAlgId,
            NULL,
            0);
        if(status != STATUS_SUCCESS) {
            xmlSecMSCngNtError("BCryptOpenAlgorithmProvider", xmlSecTransformGetName(transform), status);
            return(-1);
        }

        /* calculate the size of the buffer to hold the hash object */
        status = BCryptGetProperty(
            ctx->hAlg,
            BCRYPT_OBJECT_LENGTH,
            (PBYTE)&cbHashObject,
            sizeof(DWORD),
            &cbData,
            0);
        if(status != STATUS_SUCCESS) {
            xmlSecMSCngNtError("BCryptGetProperty", xmlSecTransformGetName(transform), status);
            return(-1);
        }

        /* allocate the hash object on the heap */
        ctx->pbHashObject = (PBYTE)xmlMalloc(cbHashObject);
        if(ctx->pbHashObject == NULL) {
            xmlSecMallocError(cbHashObject, NULL);
            return(-1);
        }

        /* calculate the length of the hash */
        status = BCryptGetProperty(
            ctx->hAlg,
            BCRYPT_HASH_LENGTH,
            (PBYTE)&ctx->cbHash,
            sizeof(DWORD),
            &cbData,
            0);
        if(status != STATUS_SUCCESS) {
            xmlSecMSCngNtError("BCryptGetProperty", xmlSecTransformGetName(transform), status);
            return(-1);
        }

        /* allocate the hash buffer on the heap */
        ctx->pbHash = (PBYTE)xmlMalloc(ctx->cbHash);
        if(ctx->pbHash == NULL) {
            xmlSecMallocError(ctx->cbHash, NULL);
            return(-1);
        }

        /* create the hash */
        status = BCryptCreateHash(
            ctx->hAlg,
            &ctx->hHash,
            ctx->pbHashObject,
            cbHashObject,
            NULL,
            0,
            0);
        if(status != STATUS_SUCCESS) {
            xmlSecMSCngNtError("BCryptCreateHash", xmlSecTransformGetName(transform), status);
            return(-1);
        }

        transform->status = xmlSecTransformStatusWorking;
    }

    if(transform->status == xmlSecTransformStatusWorking) {
        xmlSecSize inSize;

        inSize = xmlSecBufferGetSize(in);
        if(inSize > 0) {
            DWORD dwInSize;

            /* hash some data */
            XMLSEC_SAFE_CAST_SIZE_TO_ULONG(inSize, dwInSize, return(-1), xmlSecTransformGetName(transform));
            status = BCryptHashData(
                ctx->hHash,
                (PBYTE)xmlSecBufferGetData(in),
                dwInSize,
                0);
            if(status != STATUS_SUCCESS) {
                xmlSecMSCngNtError("BCryptHashData", xmlSecTransformGetName(transform), status);
                return(-1);
            }

            ret = xmlSecBufferRemoveHead(in, inSize);
            if(ret < 0) {
                xmlSecInternalError("xmlSecBufferRemoveHead", xmlSecTransformGetName(transform));
                return(-1);
            }
        }

        if(last) {
            /* close the hash */
            status = BCryptFinishHash(
                ctx->hHash,
                ctx->pbHash,
                ctx->cbHash,
                0);
            if(status != STATUS_SUCCESS) {
                xmlSecMSCngNtError("BCryptFinishHash", xmlSecTransformGetName(transform), status);
                return(-1);
            }

            xmlSecAssert2(ctx->cbHash > 0, -1);

            /* copy result to output */
            if(transform->operation == xmlSecTransformOperationSign) {
                ret = xmlSecBufferAppend(out, ctx->pbHash, ctx->cbHash);
                if(ret < 0) {
                    xmlSecInternalError("xmlSecBufferAppend",
                                         xmlSecTransformGetName(transform));
                    return(-1);
                }
            }
            transform->status = xmlSecTransformStatusFinished;
        }
    } else if(transform->status == xmlSecTransformStatusFinished) {
        xmlSecAssert2(xmlSecBufferGetSize(in) == 0, -1);
    } else {
        xmlSecInvalidTransfromStatusError(transform);
        return(-1);
    }

    return(0);
}

#ifndef XMLSEC_NO_MD5
/******************************************************************************
 *
 * MD5
 *
 *****************************************************************************/
static xmlSecTransformKlass xmlSecMSCngMd5Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),              /* size_t klassSize */
    xmlSecMSCngDigestSize,                     /* size_t objSize */

    xmlSecNameMd5,                             /* const xmlChar* name; */
    xmlSecHrefMd5,                             /* const xmlChar* href; */
    xmlSecTransformUsageDigestMethod,          /* xmlSecTransformUsage usage; */
    xmlSecMSCngDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecMSCngDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    NULL,                                      /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                      /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                      /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                      /* xmlSecTransformSetKeyMethod setKey; */
    xmlSecMSCngDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */
    xmlSecTransformDefaultGetDataType,         /* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,             /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,              /* xmlSecTransformPopBinMethod popBin; */
    NULL,                                      /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                      /* xmlSecTransformPopXmlMethod popXml; */
    xmlSecMSCngDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */
    NULL,                                      /* void* reserved0; */
    NULL,                                      /* void* reserved1; */
};

/**
 * xmlSecMSCngTransformMd5GetKlass:
 *
 * MD-5 digest transform klass.
 *
 * Returns: pointer to MD-5 digest transform klass.
 */
xmlSecTransformId
xmlSecMSCngTransformMd5GetKlass(void) {
    return(&xmlSecMSCngMd5Klass);
}
#endif /* XMLSEC_NO_MD5 */

#ifndef XMLSEC_NO_SHA1
/******************************************************************************
 *
 * SHA1
 *
 *****************************************************************************/
static xmlSecTransformKlass xmlSecMSCngSha1Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),              /* size_t klassSize */
    xmlSecMSCngDigestSize,                  /* size_t objSize */

    xmlSecNameSha1,                          /* const xmlChar* name; */
    xmlSecHrefSha1,                          /* const xmlChar* href; */
    xmlSecTransformUsageDigestMethod,          /* xmlSecTransformUsage usage; */
    xmlSecMSCngDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecMSCngDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    NULL,                                      /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                      /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                      /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                      /* xmlSecTransformSetKeyMethod setKey; */
    xmlSecMSCngDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */
    xmlSecTransformDefaultGetDataType,         /* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,             /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,              /* xmlSecTransformPopBinMethod popBin; */
    NULL,                                      /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                      /* xmlSecTransformPopXmlMethod popXml; */
    xmlSecMSCngDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */
    NULL,                                      /* void* reserved0; */
    NULL,                                      /* void* reserved1; */
};

/**
 * xmlSecMSCngTransformSha1GetKlass:
 *
 * SHA-1 digest transform klass.
 *
 * Returns: pointer to SHA-1 digest transform klass.
 */
xmlSecTransformId
xmlSecMSCngTransformSha1GetKlass(void) {
    return(&xmlSecMSCngSha1Klass);
}
#endif /* XMLSEC_NO_SHA1 */

#ifndef XMLSEC_NO_SHA256
/******************************************************************************
 *
 * SHA256
 *
 *****************************************************************************/
static xmlSecTransformKlass xmlSecMSCngSha256Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),              /* size_t klassSize */
    xmlSecMSCngDigestSize,                  /* size_t objSize */

    xmlSecNameSha256,                          /* const xmlChar* name; */
    xmlSecHrefSha256,                          /* const xmlChar* href; */
    xmlSecTransformUsageDigestMethod,          /* xmlSecTransformUsage usage; */
    xmlSecMSCngDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecMSCngDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    NULL,                                      /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                      /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                      /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                      /* xmlSecTransformSetKeyMethod setKey; */
    xmlSecMSCngDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */
    xmlSecTransformDefaultGetDataType,         /* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,             /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,              /* xmlSecTransformPopBinMethod popBin; */
    NULL,                                      /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                      /* xmlSecTransformPopXmlMethod popXml; */
    xmlSecMSCngDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */
    NULL,                                      /* void* reserved0; */
    NULL,                                      /* void* reserved1; */
};

/**
 * xmlSecMSCngTransformSha256GetKlass:
 *
 * SHA-256 digest transform klass.
 *
 * Returns: pointer to SHA-256 digest transform klass.
 */
xmlSecTransformId
xmlSecMSCngTransformSha256GetKlass(void) {
    return(&xmlSecMSCngSha256Klass);
}
#endif /* XMLSEC_NO_SHA256 */

#ifndef XMLSEC_NO_SHA384
/******************************************************************************
 *
 * SHA384
 *
 *****************************************************************************/
static xmlSecTransformKlass xmlSecMSCngSha384Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),              /* size_t klassSize */
    xmlSecMSCngDigestSize,                  /* size_t objSize */

    xmlSecNameSha384,                          /* const xmlChar* name; */
    xmlSecHrefSha384,                          /* const xmlChar* href; */
    xmlSecTransformUsageDigestMethod,          /* xmlSecTransformUsage usage; */
    xmlSecMSCngDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecMSCngDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    NULL,                                      /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                      /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                      /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                      /* xmlSecTransformSetKeyMethod setKey; */
    xmlSecMSCngDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */
    xmlSecTransformDefaultGetDataType,         /* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,             /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,              /* xmlSecTransformPopBinMethod popBin; */
    NULL,                                      /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                      /* xmlSecTransformPopXmlMethod popXml; */
    xmlSecMSCngDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */
    NULL,                                      /* void* reserved0; */
    NULL,                                      /* void* reserved1; */
};

/**
 * xmlSecMSCngTransformSha384GetKlass:
 *
 * SHA-256 digest transform klass.
 *
 * Returns: pointer to SHA-256 digest transform klass.
 */
xmlSecTransformId
xmlSecMSCngTransformSha384GetKlass(void) {
    return(&xmlSecMSCngSha384Klass);
}
#endif /* XMLSEC_NO_SHA384 */

#ifndef XMLSEC_NO_SHA512
/******************************************************************************
 *
 * SHA512
 *
 *****************************************************************************/
static xmlSecTransformKlass xmlSecMSCngSha512Klass = {
    /* klass/object sizes */
    sizeof(xmlSecTransformKlass),              /* size_t klassSize */
    xmlSecMSCngDigestSize,                  /* size_t objSize */

    xmlSecNameSha512,                          /* const xmlChar* name; */
    xmlSecHrefSha512,                          /* const xmlChar* href; */
    xmlSecTransformUsageDigestMethod,          /* xmlSecTransformUsage usage; */
    xmlSecMSCngDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */
    xmlSecMSCngDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */
    NULL,                                      /* xmlSecTransformNodeReadMethod readNode; */
    NULL,                                      /* xmlSecTransformNodeWriteMethod writeNode; */
    NULL,                                      /* xmlSecTransformSetKeyReqMethod setKeyReq; */
    NULL,                                      /* xmlSecTransformSetKeyMethod setKey; */
    xmlSecMSCngDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */
    xmlSecTransformDefaultGetDataType,         /* xmlSecTransformGetDataTypeMethod getDataType; */
    xmlSecTransformDefaultPushBin,             /* xmlSecTransformPushBinMethod pushBin; */
    xmlSecTransformDefaultPopBin,              /* xmlSecTransformPopBinMethod popBin; */
    NULL,                                      /* xmlSecTransformPushXmlMethod pushXml; */
    NULL,                                      /* xmlSecTransformPopXmlMethod popXml; */
    xmlSecMSCngDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */
    NULL,                                      /* void* reserved0; */
    NULL,                                      /* void* reserved1; */
};

/**
 * xmlSecMSCngTransformSha512GetKlass:
 *
 * SHA-512 digest transform klass.
 *
 * Returns: pointer to SHA-512 digest transform klass.
 */
xmlSecTransformId
xmlSecMSCngTransformSha512GetKlass(void) {
    return(&xmlSecMSCngSha512Klass);
}
#endif /* XMLSEC_NO_SHA512 */
