/*
* 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 <tcl.h>
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/params.h>
#endif
/* Macros */
#define BUFFER_SIZE 32768
/* 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
/*******************************************************************/
/*
* This structure defines the per-instance state of a encrypt operation.
*/
typedef struct EncryptState {
int type; /* Operation type */
Tcl_Interp *interp; /* Current interpreter */
EVP_CIPHER_CTX *ctx; /* Cipher Context */
Tcl_Command token; /* Command token */
} EncryptState;
/*
*-------------------------------------------------------------------
*
* EncryptStateNew --
*
* This function creates a per-instance state data structure
*
* Returns:
* State structure pointer
*
* Side effects:
* Creates structure
*
*-------------------------------------------------------------------
*/
EncryptState *EncryptStateNew(Tcl_Interp *interp, int type) {
EncryptState *statePtr = (EncryptState *) ckalloc((unsigned) sizeof(EncryptState));
if (statePtr != NULL) {
memset(statePtr, 0, sizeof(EncryptState));
statePtr->type = type; /* Operation type */
statePtr->interp = interp; /* Current interpreter */
statePtr->ctx = NULL; /* Cipher Context */
statePtr->token = NULL; /* Command token */
}
return statePtr;
}
/*
*-------------------------------------------------------------------
*
* EncryptStateFree --
*
* This function deletes a state data structure
*
* Returns:
* Nothing
*
* Side effects:
* Removes structure
*
*-------------------------------------------------------------------
*/
void EncryptStateFree(EncryptState *statePtr) {
if (statePtr == (EncryptState *) NULL) {
return;
}
/* Free context structures */
if (statePtr->ctx != (EVP_CIPHER_CTX *) NULL) {
EVP_CIPHER_CTX_free(statePtr->ctx);
}
ckfree(statePtr);
}
/*******************************************************************/
/*
*-------------------------------------------------------------------
*
* 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) {
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) {
return TCL_OK;
} else {
Tcl_AppendResult(interp, "Finalize failed: ", REASON(), NULL);
return TCL_ERROR;
}
}
/*******************************************************************/
/*
*-------------------------------------------------------------------
*
* EncryptInstanceObjCmd --
*
* Handler for encrypt/decrypt command instances. Used to update
* and finalize data for encrypt/decrypt function.
*
* Returns:
* TCL_OK or TCL_ERROR
*
* Side effects:
* Adds data to encrypt/decrypt function
*
*-------------------------------------------------------------------
*/
int EncryptInstanceObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
EncryptState *statePtr = (EncryptState *) clientData;
int fn, data_len = 0, out_len;
char *data = NULL;
Tcl_Obj *resultObj;
unsigned char *outbuf;
static const char *instance_fns [] = { "finalize", "update", NULL };
dprintf("Called");
/* Validate arg count */
if (objc < 2 || objc > 3) {
Tcl_WrongNumArgs(interp, 1, objv, "function ?data?");
return TCL_ERROR;
}
/* Get function */
if (Tcl_GetIndexFromObj(interp, objv[1], instance_fns, "function", 0, &fn) != TCL_OK) {
return TCL_ERROR;
}
/* Allocate storage for result. Size should be data size + block size. */
resultObj = Tcl_NewObj();
outbuf = Tcl_SetByteArrayLength(resultObj, data_len+EVP_MAX_BLOCK_LENGTH);
if (resultObj == NULL || outbuf == NULL) {
Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
Tcl_DecrRefCount(resultObj);
return TCL_ERROR;
}
/* Do function */
if (fn) {
/* Get data or return error if none */
if (objc == 3) {
data = Tcl_GetByteArrayFromObj(objv[2], &data_len);
} else {
Tcl_WrongNumArgs(interp, 1, objv, "update data");
Tcl_DecrRefCount(resultObj);
return TCL_ERROR;
}
/* Update function */
if (EncryptUpdate(interp, statePtr->type, statePtr->ctx, outbuf, &out_len, data, data_len) == TCL_OK) {
outbuf = Tcl_SetByteArrayLength(resultObj, out_len);
Tcl_SetObjResult(interp, resultObj);
} else {
Tcl_DecrRefCount(resultObj);
return TCL_ERROR;
}
} else {
/* Finalize function */
if (EncryptFinalize(interp, statePtr->type, statePtr->ctx, outbuf, &out_len) == TCL_OK) {
outbuf = Tcl_SetByteArrayLength(resultObj, out_len);
Tcl_SetObjResult(interp, resultObj);
} else {
Tcl_DecrRefCount(resultObj);
return TCL_ERROR;
}
/* Clean-up */
Tcl_DeleteCommandFromToken(interp, statePtr->token);
}
return TCL_OK;
}
/*
*-------------------------------------------------------------------
*
* EncryptCommandDeleteHandler --
*
* Callback to clean-up when encrypt/decrypt command is deleted.
*
* Returns:
* Nothing
*
* Side effects:
* Destroys state info structure
*
*-------------------------------------------------------------------
*/
void EncryptCommandDeleteHandler(ClientData clientData) {
EncryptState *statePtr = (EncryptState *) clientData;
/* Clean-up */
EncryptStateFree(statePtr);
}
/*
*-------------------------------------------------------------------
*
* EncryptCommandHandler --
*
* Create command to add data to encrypt/decrypt function.
*
* Returns:
* TCL_OK or TCL_ERROR
*
* Side effects:
* Creates command or error message
*
*-------------------------------------------------------------------
*/
int EncryptCommandHandler(Tcl_Interp *interp, int type, Tcl_Obj *cmdObj,
Tcl_Obj *cipherObj, Tcl_Obj *digestObj, Tcl_Obj *keyObj, Tcl_Obj *ivObj) {
EncryptState *statePtr;
char *cmdName = Tcl_GetStringFromObj(cmdObj, NULL);
dprintf("Called");
if ((statePtr = EncryptStateNew(interp, type)) == NULL) {
Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
return TCL_ERROR;
}
/* Initialize function */
if (EncryptInitialize(interp, type, &statePtr->ctx, cipherObj, keyObj, ivObj) != TCL_OK) {
EncryptStateFree(statePtr);
return TCL_ERROR;
}
/* Create instance command */
statePtr->token = Tcl_CreateObjCommand(interp, cmdName, EncryptInstanceObjCmd,
(ClientData) statePtr, EncryptCommandDeleteHandler);
/* Return command name */
Tcl_SetObjResult(interp, cmdObj);
return TCL_OK;
}
/*******************************************************************/
/*
*-------------------------------------------------------------------
*
* 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, len = 0, res = TCL_OK;
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 result. Size should be data size + block size. */
resultObj = Tcl_NewObj();
outbuf = Tcl_SetByteArrayLength(resultObj, data_len+EVP_MAX_BLOCK_LENGTH);
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, &len) != TCL_OK) {
res = TCL_ERROR;
goto done;
}
out_len += len;
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;
}
/*******************************************************************/
/*
*-------------------------------------------------------------------
*
* EncryptFileHandler --
*
* Perform encryption function on a block of data and return result.
*
* Returns:
* TCL_OK or TCL_ERROR
*
* Side effects:
* Encrypts or decrypts inFile data to outFile and sets result to
* size of outFile, or an error message.
*
*-------------------------------------------------------------------
*/
int EncryptFileHandler(Tcl_Interp *interp, int type, Tcl_Obj *inFileObj, Tcl_Obj *outFileObj,
Tcl_Obj *cipherObj, Tcl_Obj *digestObj, Tcl_Obj *keyObj, Tcl_Obj *ivObj) {
EVP_CIPHER_CTX *ctx = NULL;
int total = 0, res, out_len = 0, len;
Tcl_Channel in = NULL, out = NULL;
unsigned char in_buf[BUFFER_SIZE];
unsigned char out_buf[BUFFER_SIZE+EVP_MAX_BLOCK_LENGTH];
dprintf("Called");
/* Open input file */
if ((in = Tcl_FSOpenFileChannel(interp, inFileObj, "rb", 0444)) == (Tcl_Channel) NULL) {
return TCL_ERROR;
}
/* Open output file */
if ((out = Tcl_FSOpenFileChannel(interp, outFileObj, "wb", 0644)) == (Tcl_Channel) NULL) {
Tcl_Close(interp, in);
return TCL_ERROR;
}
/* Initialize operation */
if ((res = EncryptInitialize(interp, type, &ctx, cipherObj, keyObj, ivObj)) != TCL_OK) {
goto done;
}
/* Read file data from inFile, encrypt/decrypt it, then output to outFile */
while (!Tcl_Eof(in)) {
int read = Tcl_ReadRaw(in, (char *) in_buf, BUFFER_SIZE);
if (read > 0) {
if ((res = EncryptUpdate(interp, type, ctx, out_buf, &out_len, in_buf, read)) == TCL_OK) {
if (out_len > 0) {
len = Tcl_WriteRaw(out, (const char *) out_buf, out_len);
if (len >= 0) {
total += len;
} else {
Tcl_AppendResult(interp, "Write error: ", Tcl_ErrnoMsg(Tcl_GetErrno()), (char *) NULL);
res = TCL_ERROR;
goto done;
}
}
} else {
goto done;
}
} else if (read < 0) {
Tcl_AppendResult(interp, "Read error: ", Tcl_ErrnoMsg(Tcl_GetErrno()), (char *) NULL);
res = TCL_ERROR;
goto done;
}
}
/* Finalize data and write any remaining data in block */
if ((res = EncryptFinalize(interp, type, ctx, out_buf, &out_len)) == TCL_OK) {
if (out_len > 0) {
len = Tcl_WriteRaw(out, (const char *) out_buf, out_len);
if (len >= 0) {
total += len;
} else {
Tcl_AppendResult(interp, "Write error: ", Tcl_ErrnoMsg(Tcl_GetErrno()), (char *) NULL);
res = TCL_ERROR;
goto done;
}
}
Tcl_SetObjResult(interp, Tcl_NewIntObj(total));
} else {
goto done;
}
done:
/* Clean up */
if (in != NULL) {
Tcl_Close(interp, in);
}
if (out != NULL) {
Tcl_Close(interp, out);
}
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? [-command cmdName | -infile filename -outfile filename | -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("-command", cmdObj);
OPTOBJ("-data", dataObj);
OPTOBJ("-digest", digestObj);
OPTOBJ("-infile", inFileObj);
OPTOBJ("-outfile", outFileObj);
OPTOBJ("-key", keyObj);
OPTOBJ("-iv", ivObj);
OPTBAD("option", "-cipher, -command, -data, -digest, -infile, -key, -iv, -outfile");
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 (inFileObj != NULL && outFileObj != NULL) {
res = EncryptFileHandler(interp, type, inFileObj, outFileObj, cipherObj, digestObj, keyObj, ivObj);
} else if (cmdObj != NULL) {
res = EncryptCommandHandler(interp, type, cmdObj, cipherObj, digestObj, keyObj, ivObj);
} else if (dataObj != NULL) {
res = EncryptDataHandler(interp, type, dataObj, cipherObj, digestObj, keyObj, ivObj);
} else {
Tcl_AppendResult(interp, "No operation specified: Use -command, -data, -infile, or -outfile 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;
}