/* * Encryption Functions Module * * This module provides commands that can be used to encrypt or decrypt data. * * Copyright (C) 2023 Brian O'Hagan * */ #include "tlsInt.h" #include "tclOpts.h" #include #include #include #include #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #endif /* Encryption functions */ #define TYPE_MD 0x010 #define TYPE_HMAC 0x020 #define TYPE_CMAC 0x040 #define TYPE_MAC 0x080 #define TYPE_ENCRYPT 0x100 #define TYPE_DECRYPT 0x200 #define TYPE_SIGN 0x400 #define TYPE_VERIFY 0x800 /*******************************************************************/ /* *------------------------------------------------------------------- * * EncryptInitialize -- * * Initialize an encryption function * * Returns: * TCL_OK if successful or TCL_ERROR for failure with result set * to error message. * * Side effects: * No result or error message * *------------------------------------------------------------------- */ int EncryptInitialize(Tcl_Interp *interp, int type, EVP_CIPHER_CTX **ctx, Tcl_Obj *cipherObj, Tcl_Obj *keyObj, Tcl_Obj *ivObj) { const EVP_CIPHER *cipher; char *cipherName = NULL, *key = NULL, *iv = NULL; int cipher_len = 0, data_len = 0, key_len = 0, iv_len = 0, res; dprintf("Called"); /* Get encryption parameters */ if (cipherObj != NULL) { cipherName = Tcl_GetStringFromObj(cipherObj, &cipher_len); } if (keyObj != NULL) { key = Tcl_GetStringFromObj(keyObj, &key_len); } if (ivObj != NULL) { iv = Tcl_GetStringFromObj(ivObj, &iv_len); } /* Get cipher name */ #if OPENSSL_VERSION_NUMBER < 0x30000000L cipher = EVP_get_cipherbyname(cipherName); #else cipher = EVP_CIPHER_fetch(NULL, cipherName, NULL); #endif if (cipher == NULL) { Tcl_AppendResult(interp, "Invalid cipher: \"", cipherName, "\"", NULL); return TCL_ERROR; } /* Create and initialize the context */ if((*ctx = EVP_CIPHER_CTX_new()) == NULL) { Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL); return TCL_ERROR; } /* Initialize the operation. Need appropriate key and iv size. */ #if OPENSSL_VERSION_NUMBER < 0x30000000L if (type == TYPE_ENCRYPT) { res = EVP_EncryptInit_ex(*ctx, cipher, NULL, key, iv); } else { res = EVP_DecryptInit_ex(*ctx, cipher, NULL, key, iv); } #else OSSL_PARAM params[2]; int index = 0; if (iv != NULL) { params[index++] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_IV, (void *) iv, (size_t) iv_len); } params[index] = OSSL_PARAM_construct_end(); if (type == TYPE_ENCRYPT) { res = EVP_EncryptInit_ex2(ctx, cipher, key, iv, params); } else { res = EVP_DecryptInit_ex2(ctx, cipher, key, iv, params); } #endif if(!res) { Tcl_AppendResult(interp, "Initialize failed: ", REASON(), NULL); return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------- * * EncryptUpdate -- * * Update an encryption function with data * * Returns: * 1 if successful or 0 for failure * * Side effects: * Adds encrypted data to buffer or sets result to error message * *------------------------------------------------------------------- */ int EncryptUpdate(Tcl_Interp *interp, int type, EVP_CIPHER_CTX *ctx, unsigned char *outbuf, int *out_len, unsigned char *data, int data_len) { int res, len = 0; dprintf("Called"); /* Encrypt/decrypt data */ if (type == TYPE_ENCRYPT) { res = EVP_EncryptUpdate(ctx, outbuf, out_len, data, data_len); } else { res = EVP_DecryptUpdate(ctx, outbuf, out_len, data, data_len); } if (res) { *out_len += len; return TCL_OK; } else { Tcl_AppendResult(interp, "Update failed: ", REASON(), NULL); return TCL_ERROR; } } /* *------------------------------------------------------------------- * * EncryptFinalize -- * * Finalize an encryption function * * Returns: * TCL_OK if successful or TCL_ERROR for failure with result set * to error message. * * Side effects: * Adds encrypted data to buffer or sets result to error message * *------------------------------------------------------------------- */ int EncryptFinalize(Tcl_Interp *interp, int type, EVP_CIPHER_CTX *ctx, unsigned char *outbuf, int *out_len) { int res, len = 0; dprintf("Called"); /* Finalize data */ if (type == TYPE_ENCRYPT) { res = EVP_EncryptFinal_ex(ctx, outbuf, out_len); } else { res = EVP_DecryptFinal_ex(ctx, outbuf, out_len); } if (res) { *out_len += len; return TCL_OK; } else { Tcl_AppendResult(interp, "Finalize failed: ", REASON(), NULL); return TCL_ERROR; } } /*******************************************************************/ /* *------------------------------------------------------------------- * * EncryptDataHandler -- * * Perform encryption function on a block of data and return result. * * Returns: * TCL_OK or TCL_ERROR * * Side effects: * Sets result or error message * *------------------------------------------------------------------- */ int EncryptDataHandler(Tcl_Interp *interp, int type, Tcl_Obj *dataObj, Tcl_Obj *cipherObj, Tcl_Obj *digestObj, Tcl_Obj *keyObj, Tcl_Obj *ivObj) { EVP_CIPHER_CTX *ctx = NULL; int data_len = 0, out_len = 0, res; unsigned char *data, *outbuf; Tcl_Obj *resultObj; dprintf("Called"); /* Get data */ if (dataObj != NULL) { data = Tcl_GetByteArrayFromObj(dataObj, &data_len); } else { Tcl_AppendResult(interp, "No data", NULL); return TCL_ERROR; } /* Allocate storage for encrypted data. Size should be data size + block size. */ resultObj = Tcl_NewObj(); outbuf = Tcl_SetByteArrayLength(resultObj, data_len+1024); if (resultObj == NULL || outbuf == NULL) { Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL); return TCL_ERROR; } /* Perform operation */ if (EncryptInitialize(interp, type, &ctx, cipherObj, keyObj, ivObj) != TCL_OK || EncryptUpdate(interp, type, ctx, outbuf, &out_len, data, data_len) != TCL_OK || EncryptFinalize(interp, type, ctx, outbuf+out_len, &out_len) != TCL_OK) { res = TCL_ERROR; goto done; } done: /* Set output result */ if (res == TCL_OK) { outbuf = Tcl_SetByteArrayLength(resultObj, out_len); Tcl_SetObjResult(interp, resultObj); } else { Tcl_DecrRefCount(resultObj); /* Result is error message */ } /* Clean up */ if (ctx != NULL) { EVP_CIPHER_CTX_free(ctx); } return res; } /*******************************************************************/ /* *------------------------------------------------------------------- * * EncryptMain -- * * Perform encryption function and return result. * * Returns: * TCL_OK or TCL_ERROR * * Side effects: * Sets result or error message * *------------------------------------------------------------------- */ static int EncryptMain(int type, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { int res = TCL_OK; Tcl_Obj *cipherObj = NULL, *cmdObj = NULL, *dataObj = NULL, *digestObj = NULL; Tcl_Obj *inFileObj = NULL, *outFileObj = NULL, *keyObj = NULL, *ivObj = NULL, *macObj = NULL; const char *channel = NULL, *opt; const EVP_MD *md = NULL; const EVP_CIPHER *cipher = NULL; dprintf("Called"); /* Clear interp result */ Tcl_ResetResult(interp); /* Validate arg count */ if (objc < 3 || objc > 12) { Tcl_WrongNumArgs(interp, 1, objv, "-cipher name ?-digest name? ?-key key? ?-iv string? [-data data]"); return TCL_ERROR; } /* Get options */ for (int idx = 1; idx < objc; idx++) { opt = Tcl_GetStringFromObj(objv[idx], NULL); if (opt[0] != '-') { break; } OPTOBJ("-cipher", cipherObj); OPTOBJ("-data", dataObj); OPTOBJ("-digest", digestObj); OPTOBJ("-key", keyObj); OPTOBJ("-iv", ivObj); OPTBAD("option", "-cipher, -data, -digest, -key, or -iv"); return TCL_ERROR; } /* Check for required options */ if (cipherObj == NULL) { Tcl_AppendResult(interp, "No cipher", NULL); } else if (keyObj == NULL) { Tcl_AppendResult(interp, "No key", NULL); return TCL_ERROR; } /* Perform encryption function on file, stacked channel, using instance command, or data blob */ if (dataObj != NULL) { res = EncryptDataHandler(interp, type, dataObj, cipherObj, digestObj, keyObj, ivObj); } else { Tcl_AppendResult(interp, "No operation specified: Use -data option", NULL); res = TCL_ERROR; } return res; } /* *------------------------------------------------------------------- * * Encryption Commands -- * * Perform encryption function and return results * * Returns: * TCL_OK or TCL_ERROR * * Side effects: * Command dependent * *------------------------------------------------------------------- */ static int EncryptObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return EncryptMain(TYPE_ENCRYPT, interp, objc, objv); } static int DecryptObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return EncryptMain(TYPE_DECRYPT, interp, objc, objv); } /* *------------------------------------------------------------------- * * Encrypt_Initialize -- * * Create namespace, commands, and register package version * * Returns: * TCL_OK or TCL_ERROR * * Side effects: * Creates commands * *------------------------------------------------------------------- */ int Tls_EncryptCommands(Tcl_Interp *interp) { Tcl_CreateObjCommand(interp, "tls::encrypt", EncryptObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); Tcl_CreateObjCommand(interp, "tls::decrypt", DecryptObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }