/*
 * Cryptographic Utility Functions
 *
 * Provides commands to derive keys.
 *
 * Copyright (C) 2023 Brian O'Hagan
 *
 */

#include "tlsInt.h"
#include "tclOpts.h"
#include <openssl/evp.h>


/*
 *-------------------------------------------------------------------
 *
 * Util_GetCipher --
 *
 *	Get symmetric cipher from TclObj
 *
 * Returns:
 *	Pointer to type or NULL
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
EVP_CIPHER *Util_GetCipher(Tcl_Interp *interp, Tcl_Obj *cipherObj, int no_null) {
    EVP_CIPHER *cipher = NULL;
    char *name = NULL;

    if (cipherObj != NULL) {
	name = Tcl_GetStringFromObj(cipherObj, NULL);
#if OPENSSL_VERSION_NUMBER < 0x30000000L
	cipher = EVP_get_cipherbyname(name);
#else
	cipher = EVP_CIPHER_fetch(NULL, name, NULL);
#endif
	if (cipher == NULL) {
	    Tcl_AppendResult(interp, "invalid cipher \"", name, "\"", (char *) NULL);
	}
    } else if (no_null) {
	Tcl_AppendResult(interp, "no cipher", (char *) NULL);
    }
    return cipher;
}

/*
 *-------------------------------------------------------------------
 *
 * Util_GetDigest --
 *
 *	Get message digest (MD) or hash from TclObj
 *
 * Returns:
 *	Pointer to type or NULL
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
EVP_MD *Util_GetDigest(Tcl_Interp *interp, Tcl_Obj *digestObj, int no_null) {
    EVP_MD *md = NULL;
    char *name = NULL;

    if (digestObj != NULL) {
	name = Tcl_GetStringFromObj(digestObj, NULL);
#if OPENSSL_VERSION_NUMBER < 0x30000000L
	md = EVP_get_digestbyname(name);
#else
	md = EVP_MD_fetch(NULL, name, NULL);
#endif
	if (md == NULL) {
	    Tcl_AppendResult(interp, "invalid digest \"", name, "\"", (char *) NULL);
	}
    } else if (no_null) {
	Tcl_AppendResult(interp, "no digest", (char *) NULL);
    }
    return md;
}

/*
 *-------------------------------------------------------------------
 *
 * Util_GetIV --
 *
 *	Get encryption initialization vector or seed from TclObj
 *
 * Returns:
 *	Pointer to type or NULL, and size
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
unsigned char *Util_GetIV(Tcl_Interp *interp, Tcl_Obj *ivObj, int *len, int max, int no_null) {
    unsigned char *iv = NULL;
    *len = 0;

    if (ivObj != NULL) {
	iv = Tcl_GetByteArrayFromObj(ivObj, len);
    } else if (no_null) {
	Tcl_AppendResult(interp, "no initialization vector (IV)", (char *) NULL);
	return NULL;
    }

    if (max > 0 && *len > max) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("IV too long. Must be <= %d bytes", max));
	return NULL;
    }
    return iv;
}

/*
 *-------------------------------------------------------------------
 *
 * Util_GetKey --
 *
 *	Get encryption key or password from TclObj
 *
 * Returns:
 *	Pointer to type or NULL, and size
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
unsigned char *Util_GetKey(Tcl_Interp *interp, Tcl_Obj *keyObj, int *len, char *name, int max, int no_null) {
    unsigned char *key = NULL;
    *len = 0;

    if (keyObj != NULL) {
	key = Tcl_GetByteArrayFromObj(keyObj, len);
    } else if (no_null) {
	Tcl_AppendResult(interp, "no ", name, (char *) NULL);
	return NULL;
    }

    if (max > 0 && *len > max) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid %s length. Must be <= %d bytes", name, max));
	return NULL;
    }
    return key;
}

/*
 *-------------------------------------------------------------------
 *
 * Util_GetMAC --
 *
 *	Get Message Authentication Code (MAC) from TclObj
 *
 * Returns:
 *	Pointer to type or NULL
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_MAC *Util_GetMAC(Tcl_Interp *interp, Tcl_Obj *MacObj, int no_null) {
    EVP_MAC *mac = NULL;
    char *name = NULL;

    if (MacObj != NULL) {
	name = Tcl_GetStringFromObj(MacObj, NULL);
	mac = EVP_MAC_fetch(NULL, name, NULL);
	if (mac == NULL) {
	    Tcl_AppendResult(interp, "invalid MAC \"", name, "\"", (char *) NULL);
	    return NULL;
	}
    } else if (no_null) {
	Tcl_AppendResult(interp, "no MAC", (char *) NULL);
    }
    return mac;
}
#endif

/*
 *-------------------------------------------------------------------
 *
 * Util_GetSalt --
 *
 *	Get encryption salt from TclObj
 *
 * Returns:
 *	Pointer to type or NULL, and size
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
unsigned char *Util_GetSalt(Tcl_Interp *interp, Tcl_Obj *saltObj, int *len, int max, int no_null) {
    unsigned char *salt = NULL;
    *len = 0;

    if (saltObj != NULL) {
	salt = Tcl_GetByteArrayFromObj(saltObj, len);
    } else if (no_null) {
	Tcl_AppendResult(interp, "no salt", (char *) NULL);
	return NULL;
    }

    if (max > 0 && *len > max) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Salt too long. Must be <= %d bytes", max));
	return NULL;
    }
    return salt;
}

/*******************************************************************/

/*
 *-------------------------------------------------------------------
 *
 * Util_GetBinaryArray --
 *
 *	Get binary array from TclObj
 *
 * Returns:
 *	Pointer to type or NULL, and size
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */
unsigned char *Util_GetBinaryArray(Tcl_Interp *interp, Tcl_Obj *dataObj, int *len, char *name, int min, int max, int no_null) {
    unsigned char *data = NULL;
    *len = 0;

    if (dataObj != NULL) {
	data = Tcl_GetByteArrayFromObj(dataObj, len);
    } else if (no_null) {
	Tcl_AppendResult(interp, "no ", name, (char *) NULL);
	return NULL;
    }

    if (*len < min) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid length for \"%s\": must be >= %d", name, min));
	return NULL;
    } else if (max > 0 && *len > max) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid length for \"%s\": must be <= %d", name, max));
	return NULL;
    }
    return data;
}

/*
 *-------------------------------------------------------------------
 *
 * Util_GetInt --
 *
 *	Get integer value from TclObj
 *
 * Returns:
 *	TCL_OK or TCL_ERROR
 *
 * Side effects:
 *	None
 *
 *-------------------------------------------------------------------
 */

int Util_GetInt(Tcl_Interp *interp, Tcl_Obj *dataObj, int *value, char *name, int min, int max) {

    if (dataObj != NULL) {
	if (Tcl_GetIntFromObj(interp, dataObj, value) != TCL_OK) {
	    return TCL_ERROR;
	}
    }

    /* Validate range */
    if (*value < min) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("invalid value \"%d\" for option \"%s\": must be >= %d", *value, name, min));
	return TCL_ERROR;
    } else if (max > 0 && *value > max) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf("invalid value \"%d\" for option \"%s\": must be <= %d", *value, name, max));
	return TCL_ERROR;
    }
    return TCL_OK;
}

