/**
 * XML Security Library (http://www.aleksey.com/xmlsec).
 *
 * This is free software; see the Copyright file in the source distribution for precise wording.
 *
 * Copyright (C) 2002-2026 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved.
 */
/**
 * @addtogroup xmlsec_gnutls_crypto
 * @brief Digests transforms implementation for GnuTLS.
 */
#include "globals.h"

#include <string.h>

#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>
#include <gnutls/crypto.h>

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

#include <xmlsec/gnutls/app.h>
#include <xmlsec/gnutls/crypto.h>


#include "../cast_helpers.h"

/******************************************************************************
 *
 * Internal GNUTLS Digest CTX
 *
  *****************************************************************************/
typedef struct _xmlSecGnuTLSDigestCtx              xmlSecGnuTLSDigestCtx, *xmlSecGnuTLSDigestCtxPtr;
struct _xmlSecGnuTLSDigestCtx {
    gnutls_hash_hd_t            hash;
    gnutls_digest_algorithm_t   dgstAlgo;
    xmlSecSize                  dgstSize;
    xmlSecByte                  dgst[XMLSEC_GNUTLS_MAX_DIGEST_SIZE];
};

/******************************************************************************
 *
 * Digest transforms
 *
 * xmlSecTransform + xmlSecGnuTLSDigestCtx
 *
  *****************************************************************************/
XMLSEC_TRANSFORM_DECLARE(GnuTLSDigest, xmlSecGnuTLSDigestCtx)
#define xmlSecGnuTLSDigestSize XMLSEC_TRANSFORM_SIZE(GnuTLSDigest)

static int      xmlSecGnuTLSDigestCheckId               (xmlSecTransformPtr transform);
static int      xmlSecGnuTLSDigestInitialize            (xmlSecTransformPtr transform);
static void     xmlSecGnuTLSDigestFinalize              (xmlSecTransformPtr transform);
static int      xmlSecGnuTLSDigestVerify                (xmlSecTransformPtr transform,
                                                         const xmlSecByte* data,
                                                         xmlSecSize dataSize,
                                                         xmlSecTransformCtxPtr transformCtx);
static int      xmlSecGnuTLSDigestExecute               (xmlSecTransformPtr transform,
                                                         int last,
                                                         xmlSecTransformCtxPtr transformCtx);

static int
xmlSecGnuTLSDigestCheckId(xmlSecTransformPtr transform) {


#ifndef XMLSEC_NO_SHA1
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha1Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA1 */

#ifndef XMLSEC_NO_SHA224
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha224Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA224 */

#ifndef XMLSEC_NO_SHA256
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha256Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA256 */

#ifndef XMLSEC_NO_SHA384
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha384Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA384 */

#ifndef XMLSEC_NO_SHA512
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha512Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA512 */

#ifndef XMLSEC_NO_SHA3
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_224Id)) {
        return(1);
    }
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_256Id)) {
        return(1);
    }
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_384Id)) {
        return(1);
    }
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_512Id)) {
        return(1);
    }
#endif /* XMLSEC_NO_SHA3 */

#ifndef XMLSEC_NO_GOST
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_94Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_GOST */


#ifndef XMLSEC_NO_GOST2012
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_2012_256Id)) {
        return(1);
    } else

    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_2012_512Id)) {
        return(1);
    } else
#endif /* XMLSEC_NO_GOST2012 */

    return(0);
}

static int
xmlSecGnuTLSDigestInitialize(xmlSecTransformPtr transform) {
    xmlSecGnuTLSDigestCtxPtr ctx;
    int err;

    xmlSecAssert2(xmlSecGnuTLSDigestCheckId(transform), -1);
    xmlSecAssert2(xmlSecTransformCheckSize(transform, xmlSecGnuTLSDigestSize), -1);

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

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

#ifndef XMLSEC_NO_SHA1
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha1Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA1;
    } else
#endif /* XMLSEC_NO_SHA1 */

#ifndef XMLSEC_NO_SHA224
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha224Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA224;
    } else
#endif /* XMLSEC_NO_SHA224 */

#ifndef XMLSEC_NO_SHA256
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha256Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA256;
    } else
#endif /* XMLSEC_NO_SHA256 */

#ifndef XMLSEC_NO_SHA384
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha384Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA384;
    } else
#endif /* XMLSEC_NO_SHA384 */

#ifndef XMLSEC_NO_SHA512
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha512Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA512;
    } else
#endif /* XMLSEC_NO_SHA512 */

#ifndef XMLSEC_NO_SHA3
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_224Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA3_224;
    } else
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_256Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA3_256;
    } else
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_384Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA3_384;
    } else
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformSha3_512Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_SHA3_512;
    } else
#endif /* XMLSEC_NO_SHA3 */

#ifndef XMLSEC_NO_GOST
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_94Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_GOSTR_94;
    } else
#endif /* XMLSEC_NO_GOST */

#ifndef XMLSEC_NO_GOST2012
    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_2012_256Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_STREEBOG_256;
    } else

    if(xmlSecTransformCheckId(transform, xmlSecGnuTLSTransformGostR3411_2012_512Id)) {
        ctx->dgstAlgo = GNUTLS_DIG_STREEBOG_512;
    } else
#endif /* XMLSEC_NO_GOST2012 */

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

    /* check hash output size */
    ctx->dgstSize = gnutls_hash_get_len(ctx->dgstAlgo);
    if(ctx->dgstSize <= 0) {
        xmlSecGnuTLSError("gnutls_hash_get_len", 0, NULL);
        return(-1);
    }
    xmlSecAssert2(ctx->dgstSize < XMLSEC_GNUTLS_MAX_DIGEST_SIZE, -1);

    /* create hash */
    err =  gnutls_hash_init(&(ctx->hash), ctx->dgstAlgo);
    if(err != GNUTLS_E_SUCCESS) {
        xmlSecGnuTLSError("gnutls_hash_init", err, NULL);
        return(-1);
    }

    /* done */
    return(0);
}

static void
xmlSecGnuTLSDigestFinalize(xmlSecTransformPtr transform) {
    xmlSecGnuTLSDigestCtxPtr ctx;

    xmlSecAssert(xmlSecGnuTLSDigestCheckId(transform));
    xmlSecAssert(xmlSecTransformCheckSize(transform, xmlSecGnuTLSDigestSize));

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

    if(ctx->hash != NULL) {
        gnutls_hash_deinit(ctx->hash, NULL);
    }
    memset(ctx, 0, sizeof(xmlSecGnuTLSDigestCtx));
}

static int
xmlSecGnuTLSDigestVerify(xmlSecTransformPtr transform,
                        const xmlSecByte* data, xmlSecSize dataSize,
                        xmlSecTransformCtxPtr transformCtx) {
    xmlSecGnuTLSDigestCtxPtr ctx;

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

    ctx = xmlSecGnuTLSDigestGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(ctx->dgstSize > 0, -1);

    if(dataSize != ctx->dgstSize) {
        xmlSecInvalidSizeDataError2("dataSize", dataSize,
                "dgstSize", ctx->dgstSize, "dataSize == dgstSize",
                xmlSecTransformGetName(transform));
        transform->status = xmlSecTransformStatusFail;
        return(0);
    }

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

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

static int
xmlSecGnuTLSDigestExecute(xmlSecTransformPtr transform, int last, xmlSecTransformCtxPtr transformCtx) {
    xmlSecGnuTLSDigestCtxPtr ctx;
    xmlSecBufferPtr in, out;
    xmlSecSize inSize, outSize;
    int ret;
    int err;

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

    ctx = xmlSecGnuTLSDigestGetCtx(transform);
    xmlSecAssert2(ctx != NULL, -1);
    xmlSecAssert2(ctx->hash != NULL, -1);

    in = &(transform->inBuf);
    out = &(transform->outBuf);
    inSize = xmlSecBufferGetSize(in);
    outSize = xmlSecBufferGetSize(out);

    if(transform->status == xmlSecTransformStatusNone) {
        xmlSecAssert2(outSize == 0, -1);
        transform->status = xmlSecTransformStatusWorking;
    }

    if((transform->status == xmlSecTransformStatusWorking) && (inSize > 0)) {
        xmlSecAssert2(outSize == 0, -1);

        /* update hash */
        err = gnutls_hash(ctx->hash, xmlSecBufferGetData(in), inSize);
        if(err != GNUTLS_E_SUCCESS) {
            xmlSecGnuTLSError("gnutls_hash", err, xmlSecTransformGetName(transform));
            return(-1);
        }

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

    if((transform->status == xmlSecTransformStatusWorking) && (last != 0)) {
        xmlSecAssert2(outSize == 0, -1);

        /* get hash */
        gnutls_hash_output(ctx->hash, ctx->dgst);

        ret = xmlSecBufferAppend(out, ctx->dgst, ctx->dgstSize);
        if(ret < 0) {
            xmlSecInternalError2("xmlSecBufferAppend", xmlSecTransformGetName(transform),
                "size=" XMLSEC_SIZE_FMT, ctx->dgstSize);
            return(-1);
        }
        transform->status = xmlSecTransformStatusFinished;
    }

    if(transform->status == xmlSecTransformStatusFinished) {
        /* the only way we can get here is if there is no input */
        xmlSecAssert2(xmlSecBufferGetSize(&(transform->inBuf)) == 0, -1);
    }

    return(0);
}

/* Helper macro to define the digest transform klass */
#define XMLSEC_GNUTLS_DIGEST_KLASS(name)                                                                \
static xmlSecTransformKlass xmlSecGnuTLS ## name ## Klass = {                                          \
    sizeof(xmlSecTransformKlass),               /* xmlSecSize klassSize */                              \
    xmlSecGnuTLSDigestSize,                     /* xmlSecSize objSize */                                \
    xmlSecName ## name,                         /* const xmlChar* name; */                              \
    xmlSecHref ## name,                         /* const xmlChar* href; */                              \
    xmlSecTransformUsageDigestMethod,           /* xmlSecTransformUsage usage; */                       \
    xmlSecGnuTLSDigestInitialize,               /* xmlSecTransformInitializeMethod initialize; */       \
    xmlSecGnuTLSDigestFinalize,                 /* xmlSecTransformFinalizeMethod finalize; */           \
    NULL,                                       /* xmlSecTransformNodeReadMethod readNode; */           \
    NULL,                                       /* xmlSecTransformNodeWriteMethod writeNode; */         \
    NULL,                                       /* xmlSecTransformSetKeyReqMethod setKeyReq; */         \
    NULL,                                       /* xmlSecTransformSetKeyMethod setKey; */               \
    xmlSecGnuTLSDigestVerify,                   /* xmlSecTransformVerifyMethod verify; */               \
    xmlSecTransformDefaultGetDataType,          /* xmlSecTransformGetDataTypeMethod getDataType; */     \
    xmlSecTransformDefaultPushBin,              /* xmlSecTransformPushBinMethod pushBin; */             \
    xmlSecTransformDefaultPopBin,               /* xmlSecTransformPopBinMethod popBin; */               \
    NULL,                                       /* xmlSecTransformPushXmlMethod pushXml; */             \
    NULL,                                       /* xmlSecTransformPopXmlMethod popXml; */               \
    xmlSecGnuTLSDigestExecute,                  /* xmlSecTransformExecuteMethod execute; */             \
    NULL,                                       /* void* reserved0; */                                  \
    NULL,                                       /* void* reserved1; */                                  \
};


#ifndef XMLSEC_NO_SHA1
/******************************************************************************
 *
 * SHA1 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha1)

/**
 * @brief SHA-1 digest transform klass.
 * @return pointer to SHA-1 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha1GetKlass(void) {
    return(&xmlSecGnuTLSSha1Klass);
}
#endif /* XMLSEC_NO_SHA1 */

#ifndef XMLSEC_NO_SHA224
/******************************************************************************
 *
 * SHA2-224 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha224)

/**
 * @brief SHA2-224 digest transform klass.
 * @return pointer to SHA2-224 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha224GetKlass(void) {
    return(&xmlSecGnuTLSSha224Klass);
}
#endif /* XMLSEC_NO_SHA224 */

#ifndef XMLSEC_NO_SHA256
/******************************************************************************
 *
 * SHA2-256 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha256)

/**
 * @brief SHA2-256 digest transform klass.
 * @return pointer to SHA2-256 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha256GetKlass(void) {
    return(&xmlSecGnuTLSSha256Klass);
}
#endif /* XMLSEC_NO_SHA256 */


#ifndef XMLSEC_NO_SHA384
/******************************************************************************
 *
 * SHA2-384 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha384)

/**
 * @brief SHA2-384 digest transform klass.
 * @return pointer to SHA2-384 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha384GetKlass(void) {
    return(&xmlSecGnuTLSSha384Klass);
}
#endif /* XMLSEC_NO_SHA384 */

#ifndef XMLSEC_NO_SHA512
/******************************************************************************
 *
 * SHA2-512 digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha512)

/**
 * @brief SHA2-512 digest transform klass.
 * @return pointer to SHA2-512 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha512GetKlass(void) {
    return(&xmlSecGnuTLSSha512Klass);
}
#endif /* XMLSEC_NO_SHA512 */


#ifndef XMLSEC_NO_SHA3
/******************************************************************************
 *
 * SHA3-224 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha3_224)

/**
 * @brief SHA3-224 digest transform klass.
 * @return pointer to SHA3-224 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha3_224GetKlass(void) {
    return(&xmlSecGnuTLSSha3_224Klass);
}

/******************************************************************************
 *
 * SHA3-256 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha3_256)

/**
 * @brief SHA3-256 digest transform klass.
 * @return pointer to SHA3-256 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha3_256GetKlass(void) {
    return(&xmlSecGnuTLSSha3_256Klass);
}

/******************************************************************************
 *
 * SHA3-384 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha3_384)

/**
 * @brief SHA3-384 digest transform klass.
 * @return pointer to SHA3-384 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha3_384GetKlass(void) {
    return(&xmlSecGnuTLSSha3_384Klass);
}

/******************************************************************************
 *
 * SHA3-512 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(Sha3_512)

/**
 * @brief SHA3-512 digest transform klass.
 * @return pointer to SHA3-512 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformSha3_512GetKlass(void) {
    return(&xmlSecGnuTLSSha3_512Klass);
}
#endif /* XMLSEC_NO_SHA3 */

#ifndef XMLSEC_NO_GOST
/******************************************************************************
 *
 * GOSTR3411_94 Digest transforms
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(GostR3411_94)

/**
 * @brief GOSTR3411_94 digest transform klass.
 * @return pointer to GOSTR3411_94 digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformGostR3411_94GetKlass(void) {
    return(&xmlSecGnuTLSGostR3411_94Klass);
}
#endif /* XMLSEC_NO_GOST */


#ifndef XMLSEC_NO_GOST2012
/******************************************************************************
 *
 * GOST R 34.11-2012 256 bit
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(GostR3411_2012_256)

/**
 * @brief GOST R 34.11-2012 256 bit digest transform klass.
 * @return pointer to GOST R 34.11-2012 256 bit digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformGostR3411_2012_256GetKlass(void) {
    return(&xmlSecGnuTLSGostR3411_2012_256Klass);
}

/******************************************************************************
 *
 * GOST R 34.11-2012 512 bit
 *
  *****************************************************************************/
XMLSEC_GNUTLS_DIGEST_KLASS(GostR3411_2012_512)

/**
 * @brief GOST R 34.11-2012 512 bit digest transform klass.
 * @return pointer to GOST R 34.11-2012 512 bit digest transform klass.
 */
xmlSecTransformId
xmlSecGnuTLSTransformGostR3411_2012_512GetKlass(void) {
    return(&xmlSecGnuTLSGostR3411_2012_512Klass);
}


#endif /* XMLSEC_NO_GOST2012 */
