Index: generic/tls.c
==================================================================
--- generic/tls.c
+++ generic/tls.c
@@ -1353,10 +1353,11 @@
 
     /*
      * SSL Callbacks
      */
     SSL_set_app_data(statePtr->ssl, (void *)statePtr);	/* point back to us */
+
     SSL_set_verify(statePtr->ssl, verify, VerifyCallback);
     SSL_set_info_callback(statePtr->ssl, InfoCallback);
 
     /* Callback for observing protocol messages */
 #ifndef OPENSSL_NO_SSL_TRACE
@@ -1873,10 +1874,11 @@
     if (objc == 2) {
 	peer = SSL_get_peer_certificate(statePtr->ssl);
     } else {
 	peer = SSL_get_certificate(statePtr->ssl);
     }
+
     /* Get X509 certificate info */
     if (peer) {
 	objPtr = Tls_NewX509Obj(interp, peer);
 	if (objc == 2) {
 	    X509_free(peer);
@@ -2163,10 +2165,11 @@
     /* IF not a server, same as SSL_get0_peer_CA_list. If server same as SSL_CTX_get_client_CA_list */
     listPtr = Tcl_NewListObj(0, NULL);
     STACK_OF(X509_NAME) *ca_list;
     if ((ca_list = SSL_get_client_CA_list(ssl)) != NULL) {
 	char buffer[BUFSIZ];
+
 	for (int i = 0; i < sk_X509_NAME_num(ca_list); i++) {
 	    X509_NAME *name = sk_X509_NAME_value(ca_list, i);
 	    if (name) {
 		X509_NAME_oneline(name, buffer, BUFSIZ);
 		Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj(buffer, -1));
@@ -2173,10 +2176,11 @@
 	    }
 	}
     }
     LAPPEND_OBJ(interp, objPtr, "caList", listPtr);
     LAPPEND_INT(interp, objPtr, "caListCount", sk_X509_NAME_num(ca_list));
+
 
     Tcl_SetObjResult(interp, objPtr);
     return TCL_OK;
 	clientData = clientData;
 }

Index: generic/tlsDigest.c
==================================================================
--- generic/tlsDigest.c
+++ generic/tlsDigest.c
@@ -1,7 +1,7 @@
 /*
- * Message Digests (MD) and Message Authentication Code (MAC) Module
+ * Message Digest (MD) and Message Authentication Code (MAC) Module
  *
  * Provides commands to calculate a message digest (MD) or message
  * authentication code (MAC) using a specified hash function and/or cipher.
  *
  * Copyright (C) 2023 Brian O'Hagan
@@ -54,11 +54,11 @@
 /*
  *-------------------------------------------------------------------
  *
  * Tls_DigestNew --
  *
- *	This function creates a digest state structure
+ *	This function creates a per-instance state data structure
  *
  * Returns:
  *	Digest structure pointer
  *
  * Side effects:
@@ -90,11 +90,11 @@
 /*
  *-------------------------------------------------------------------
  *
  * Tls_DigestFree --
  *
- *	This function removes a digest state structure
+ *	This function deletes a digest state structure
  *
  * Returns:
  *	Nothing
  *
  * Side effects:
@@ -105,10 +105,16 @@
 void Tls_DigestFree(DigestState *statePtr) {
     if (statePtr == (DigestState *) NULL) {
 	return;
     }
 
+    /* Remove pending timer */
+    if (statePtr->timer != (Tcl_TimerToken) NULL) {
+	Tcl_DeleteTimerHandler(statePtr->timer);
+    }
+
+    /* Free context structures */
     if (statePtr->ctx != (EVP_MD_CTX *) NULL) {
 	EVP_MD_CTX_free(statePtr->ctx);
     }
     if (statePtr->hctx != (HMAC_CTX *) NULL) {
 	HMAC_CTX_free(statePtr->hctx);
@@ -138,11 +144,11 @@
  *-------------------------------------------------------------------
  */
 int Tls_DigestInit(Tcl_Interp *interp, DigestState *statePtr, const EVP_MD *md,
 	const EVP_CIPHER *cipher, Tcl_Obj *keyObj) {
     int key_len = 0, res = 0;
-    const unsigned char *key;
+    const unsigned char *key = NULL;
 
     /* Create message digest context */
     if (statePtr->format & TYPE_MD) {
 	statePtr->ctx = EVP_MD_CTX_new();
 	res = (statePtr->ctx != NULL);
@@ -155,19 +161,22 @@
     }
     if (!res) {
 	Tcl_AppendResult(interp, "Create context failed: ", REASON(), NULL);
 	return TCL_ERROR;
     }
+
+    /* Get key */
+    if (keyObj != NULL) {
+	key = Tcl_GetByteArrayFromObj(keyObj, &key_len);
+    }
 
     /* Initialize hash function */
     if (statePtr->format & TYPE_MD) {
 	res = EVP_DigestInit_ex(statePtr->ctx, md, NULL);
     } else if (statePtr->format & TYPE_HMAC) {
-	key = Tcl_GetByteArrayFromObj(keyObj, &key_len);
 	res = HMAC_Init_ex(statePtr->hctx, (const void *) key, key_len, md, NULL);
     } else if (statePtr->format & TYPE_CMAC) {
-	key = Tcl_GetByteArrayFromObj(keyObj, &key_len);
 	res = CMAC_Init(statePtr->cctx, (const void *) key, key_len, cipher, NULL);
     }
     if (!res) {
 	Tcl_AppendResult(interp, "Initialize failed: ", REASON(), NULL);
 	return TCL_ERROR;
@@ -178,17 +187,17 @@
 /*
  *-------------------------------------------------------------------
  *
  * Tls_DigestUpdate --
  *
- *	Update a hash function
+ *	Update a hash function with data
  *
  * Returns:
  *	1 if successful or 0 for failure
  *
  * Side effects:
- *	Adds buf to hash function
+ *	Adds buf data to hash function or sets result to error message
  *
  *-------------------------------------------------------------------
  */
 int Tls_DigestUpdate(DigestState *statePtr, char *buf, size_t read, int do_result) {
     int res = 0;
@@ -210,22 +219,22 @@
 /*
  *-------------------------------------------------------------------
  *
  * Tls_DigestFinialize --
  *
- *	Finalize a hash function and generate a message digest
+ *	Finalize a hash function and return the message digest
  *
  * Returns:
  *	TCL_OK if successful or TCL_ERROR for failure with result set
  *	to error message.
  *
  * Side effects:
- *	Sets result to message digest for hash function or an error message.
+ *	Sets result to message digest or an error message.
  *
  *-------------------------------------------------------------------
  */
-int Tls_DigestFinialize(Tcl_Interp *interp, DigestState *statePtr) {
+int Tls_DigestFinialize(Tcl_Interp *interp, DigestState *statePtr, Tcl_Obj **resultObj) {
     unsigned char md_buf[EVP_MAX_MD_SIZE];
     unsigned int md_len;
     int res = 0;
 
     /* Finalize hash function and calculate message digest */
@@ -236,28 +245,42 @@
     } else if (statePtr->format & TYPE_CMAC) {
 	size_t len;
 	res = CMAC_Final(statePtr->cctx, md_buf, &len);
 	md_len = (unsigned int) len;
     }
+
     if (!res) {
-	Tcl_AppendResult(interp, "Finalize failed: ", REASON(), NULL);
+	if (resultObj == NULL) {
+	    Tcl_AppendResult(interp, "Finalize failed: ", REASON(), NULL);
+	}
 	return TCL_ERROR;
     }
 
     /* Return message digest as either a binary or hex string */
     if (statePtr->format & BIN_FORMAT) {
-	Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(md_buf, md_len));
+	if (resultObj == NULL) {
+	    Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(md_buf, md_len));
+	} else {
+	    *resultObj = Tcl_NewByteArrayObj(md_buf, md_len);
+	    Tcl_IncrRefCount(*resultObj);
+	}
 
     } else {
-	Tcl_Obj *resultObj = Tcl_NewObj();
-	unsigned char *ptr = Tcl_SetByteArrayLength(resultObj, md_len*2);
+	Tcl_Obj *newObj = Tcl_NewObj();
+	unsigned char *ptr = Tcl_SetByteArrayLength(newObj, md_len*2);
 
 	for (unsigned int i = 0; i < md_len; i++) {
 	    *ptr++ = hex[(md_buf[i] >> 4) & 0x0F];
 	    *ptr++ = hex[md_buf[i] & 0x0F];
 	}
-	Tcl_SetObjResult(interp, resultObj);
+
+	if (resultObj == NULL) {
+	    Tcl_SetObjResult(interp, newObj);
+	} else {
+	    *resultObj = newObj;
+	    Tcl_IncrRefCount(*resultObj);
+	}
     }
     return TCL_OK;
 }
 
 /*******************************************************************/
@@ -294,19 +317,18 @@
  *-------------------------------------------------------------------
  *
  * DigestCloseProc --
  *
  *	This function is invoked by the generic IO level to perform
- *	channel-type-specific cleanup when channel is closed. All
+ *	channel-type specific cleanup when the channel is closed. All
  *	queued output is flushed prior to calling this function.
  *
  * Returns:
  *	0 if successful or POSIX error code if failed.
  *
  * Side effects:
- *	Writes digest to output and closes the channel. Stores error
- *	messages in interp result using Tcl_GetChannelErrorInterp.
+ *	Deletes stored state data.
  *
  *-------------------------------------------------------------------
  */
 int DigestCloseProc(ClientData clientData, Tcl_Interp *interp) {
     DigestState *statePtr = (DigestState *) clientData;
@@ -314,10 +336,24 @@
     /* Cancel active timer, if any */
     if (statePtr->timer != (Tcl_TimerToken) NULL) {
 	Tcl_DeleteTimerHandler(statePtr->timer);
 	statePtr->timer = (Tcl_TimerToken) NULL;
     }
+
+    /* Output message digest if not already done */
+    if (!(statePtr->flags & CHAN_EOF)) {
+	Tcl_Channel parent = Tcl_GetStackedChannel(statePtr->self);
+	Tcl_Obj *resultObj;
+	int written;
+
+	if (Digest_Finalize(statePtr->interp, statePtr, &resultObj) == TCL_OK) {
+	    unsigned char *data = Tcl_GetByteArrayFromObj(resultObj, &written);
+	    Tcl_WriteRaw(parent, data, written);
+	    Tcl_DecrRefCount(resultObj);
+	}
+	statePtr->flags |= CHAN_EOF;
+    }
 
     /* Clean-up */
     Tls_DigestFree(statePtr);
     return 0;
 }
@@ -337,11 +373,11 @@
  *----------------------------------------------------------------------
  *
  * DigestInputProc --
  *
  *	Called by the generic IO system to read data from transform and
- *	place in buf.
+ *	place in buf. Transform gets data from the underlying channel.
  *
  * Returns:
  *	Total bytes read or -1 for an error along with a POSIX error
  *	code in errorCodePtr. Use EAGAIN for nonblocking and no data.
  *
@@ -351,11 +387,11 @@
  *----------------------------------------------------------------------
  */
 int DigestInputProc(ClientData clientData, char *buf, int toRead, int *errorCodePtr) {
     DigestState *statePtr = (DigestState *) clientData;
     Tcl_Channel parent;
-    int read, res = 0;
+    int read;
     *errorCodePtr = 0;
 
     /* Abort if nothing to process */
     if (toRead <= 0 || statePtr->self == (Tcl_Channel) NULL) {
 	return 0;
@@ -365,14 +401,15 @@
     parent = Tcl_GetStackedChannel(statePtr->self);
     read = Tcl_ReadRaw(parent, buf, toRead);
 
     /* Update hash function */
     if (read > 0) {
+	/* Have data */
 	if (!Tls_DigestUpdate(statePtr, buf, (size_t) read, 0)) {
 	    Tcl_SetChannelError(statePtr->self, Tcl_ObjPrintf("Update failed: %s", REASON()));
 	    *errorCodePtr = EINVAL;
-	    return -1;
+	    return 0;
 	}
 	/* This is correct */
 	read = -1;
 	*errorCodePtr = EAGAIN;
 
@@ -380,44 +417,20 @@
 	/* Error */
 	*errorCodePtr = Tcl_GetErrno();
 
     } else if (!(statePtr->flags & CHAN_EOF)) {
 	/* EOF */
-	unsigned char md_buf[EVP_MAX_MD_SIZE];
-	unsigned int md_len = 0;
-
-	/* Finalize hash function and calculate message digest */
-	if (statePtr->format & TYPE_MD) {
-	    res = EVP_DigestFinal_ex(statePtr->ctx, md_buf, &md_len);
-	} else if (statePtr->format & TYPE_HMAC) {
-	    res = HMAC_Final(statePtr->hctx, md_buf, &md_len);
-	} else if (statePtr->format & TYPE_CMAC) {
-	    size_t len;
-	    res = CMAC_Final(statePtr->cctx, md_buf, &len);
-	    md_len = (unsigned int) len;
-	}
-	if (!res) {
+	Tcl_Obj *resultObj;
+	if (Tls_DigestFinialize(statePtr->interp, statePtr, &resultObj) == TCL_OK) {
+	    unsigned char *data = Tcl_GetByteArrayFromObj(resultObj, &read);
+	    memcpy(buf, data, read);
+	    Tcl_DecrRefCount(resultObj);
+
+	} else {
 	    Tcl_SetChannelError(statePtr->self, Tcl_ObjPrintf("Finalize failed: %s", REASON()));
 	    *errorCodePtr = EINVAL;
-
-	/* Write message digest to output channel as byte array or hex string */
-	} else if (md_len > 0) {
-	    if ((statePtr->format & BIN_FORMAT) && toRead >= (int) md_len) {
-		read = md_len;
-		memcpy(buf, md_buf, read);
-
-	    } else if((statePtr->format & HEX_FORMAT) && toRead >= (int) (md_len*2)) {
-		unsigned char hex_buf[EVP_MAX_MD_SIZE*2];
-		unsigned char *ptr = hex_buf;
-
-		for (unsigned int i = 0; i < md_len; i++) {
-		    *ptr++ = hex[(md_buf[i] >> 4) & 0x0F];
-		    *ptr++ = hex[md_buf[i] & 0x0F];
-		}
-		read = md_len*2;
-		memcpy(buf, hex_buf, read);
-	    }
+	    read = 0;
 	}
 	statePtr->flags |= CHAN_EOF;
     }
     return read;
 }
@@ -426,10 +439,11 @@
  *----------------------------------------------------------------------
  *
  * DigestOutputProc --
  *
  *	Called by the generic IO system to write data in buf to transform.
+ *	The transform writes the result to the underlying channel.
  *
  * Returns:
  *	Total bytes written or -1 for an error along with a POSIX error
  *	code in errorCodePtr. Use EAGAIN for nonblocking and can't write data.
  *
@@ -449,11 +463,11 @@
 
     /* Update hash function */
     if (toWrite > 0 && !Tls_DigestUpdate(statePtr, buf, (size_t) toWrite, 0)) {
 	Tcl_SetChannelError(statePtr->self, Tcl_ObjPrintf("Update failed: %s", REASON()));
 	*errorCodePtr = EINVAL;
-	return -1;
+	return 0;
     }
     return toWrite;
 }
 
 /*
@@ -728,11 +742,11 @@
     }
 
     /* Make sure to operate on the topmost channel */
     chan = Tcl_GetTopChannel(chan);
 
-    /* Create state data struct */
+    /* Create state data structure */
     if ((statePtr = Tls_DigestNew(interp, format)) == NULL) {
 	Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
 	return TCL_ERROR;
     }
     statePtr->self = chan;
@@ -747,11 +761,11 @@
     Tcl_SetChannelOption(interp, chan, "-translation", "binary");
     if (Tcl_GetChannelBufferSize(chan) < EVP_MAX_MD_SIZE * 2) {
 	Tcl_SetChannelBufferSize(chan, EVP_MAX_MD_SIZE * 2);
     }
 
-    /* Stack channel */
+    /* Stack channel, abort for error */
     statePtr->self = Tcl_StackChannel(interp, &digestChannelType, (ClientData) statePtr, mode, chan);
     if (statePtr->self == (Tcl_Channel) NULL) {
 	Tls_DigestFree(statePtr);
 	return TCL_ERROR;
     }
@@ -861,11 +875,11 @@
 	    return TCL_ERROR;
 	}
 
     } else {
 	/* Finalize hash function and calculate message digest */
-	if (Tls_DigestFinialize(interp, statePtr) != TCL_OK) {
+	if (Tls_DigestFinialize(interp, statePtr, NULL) != TCL_OK) {
 	    return TCL_ERROR;
 	}
 
 	Tcl_DeleteCommandFromToken(interp, statePtr->token);
     }
@@ -912,11 +926,11 @@
 int Tls_DigestInstance(Tcl_Interp *interp, Tcl_Obj *cmdObj, const EVP_MD *md,
 	const EVP_CIPHER *cipher, int format, Tcl_Obj *keyObj) {
     DigestState *statePtr;
     char *cmdName = Tcl_GetStringFromObj(cmdObj, NULL);
 
-    /* Create state data struct */
+    /* Create state data structure */
     if ((statePtr = Tls_DigestNew(interp, format)) == NULL) {
 	Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
 	return TCL_ERROR;
     }
 
@@ -964,23 +978,25 @@
     if (data == NULL || data_len == 0) {
 	Tcl_SetResult(interp, "No data", NULL);
 	return TCL_ERROR;
     }
 
-    /* Create state struct */
+    /* Create state data structure */
     if ((statePtr = Tls_DigestNew(interp, format)) == NULL) {
 	Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
 	return TCL_ERROR;
     }
 
-    /* Calc Digest */
+    /* Calc Digest, abort for error */
     if (Tls_DigestInit(interp, statePtr, md, cipher, keyObj) != TCL_OK ||
-	Tls_DigestUpdate(statePtr, data, (size_t) len, 1) == 0 ||
-	Tls_DigestFinialize(interp, statePtr) != TCL_OK) {
+	Tls_DigestUpdate(statePtr, data, (size_t) data_len, 1) == 0 ||
+	Tls_DigestFinialize(interp, statePtr, NULL) != TCL_OK) {
 	Tls_DigestFree(statePtr);
 	return TCL_ERROR;
     }
+
+    /* Clean-up */
     Tls_DigestFree(statePtr);
     return TCL_OK;
 }
 
 /*******************************************************************/
@@ -1005,17 +1021,17 @@
     DigestState *statePtr;
     Tcl_Channel chan = NULL;
     unsigned char buf[BUFFER_SIZE];
     int res = TCL_OK, len;
 
-    /* Create state data struct */
+    /* Create state data structure */
     if ((statePtr = Tls_DigestNew(interp, format)) == NULL) {
 	Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL);
 	return TCL_ERROR;
     }
 
-    /* Open file channel */
+    /* Open file channel, abort for error */
     chan = Tcl_FSOpenFileChannel(interp, filename, "rb", 0444);
     if (chan == (Tcl_Channel) NULL) {
 	Tls_DigestFree(statePtr);
 	return TCL_ERROR;
     }
@@ -1041,11 +1057,11 @@
 	    }
 	}
     }
 
     /* Finalize hash function and calculate message digest */
-    res = Tls_DigestFinialize(interp, statePtr);
+    res = Tls_DigestFinialize(interp, statePtr, NULL);
 
 done:
     /* Close channel */
     if (Tcl_Close(interp, chan) == TCL_ERROR) {
 	res = TCL_ERROR;
@@ -1073,11 +1089,11 @@
  *	Sets result to message digest or error message
  *
  *-------------------------------------------------------------------
  */
 static int DigestMain(int type, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    int idx, len, format = HEX_FORMAT, res = TCL_OK, flags = 0;
+    int idx, format = HEX_FORMAT, res = TCL_OK, flags = 0;
     const char *digestName, *channel = NULL;
     Tcl_Obj *cmdObj = NULL, *dataObj = NULL, *fileObj = NULL, *keyObj = NULL;
     unsigned char *cipherName = NULL;
     const EVP_MD *md = NULL;
     const EVP_CIPHER *cipher = NULL;
@@ -1101,11 +1117,11 @@
 	    return TCL_ERROR;
 	}
     }
 
     /* Get options */
-    for (idx = 2; idx < objc-1; idx++) {
+    for (idx = 1; idx < objc; idx++) {
 	char *opt = Tcl_GetStringFromObj(objv[idx], NULL);
 
 	if (opt[0] != '-') {
 	    break;
 	}
@@ -1126,15 +1142,10 @@
 
 	OPTBAD("option", "-bin, -channel, -cipher, -command, -data, -digest, -file, -filename, -hex, or -key");
 	return TCL_ERROR;
     }
 
-    /* If no option for last arg, then its the data */
-    if (idx < objc) {
-	dataObj = objv[idx];
-    }
-
     /* Get cipher */
     if (cipherName != NULL) {
 	cipher = EVP_get_cipherbyname(cipherName);
 	type = TYPE_CMAC;
 	if (cipher == NULL) {
@@ -1222,47 +1233,39 @@
  * Side effects:
  *	Sets result to message digest or error message
  *
  *-------------------------------------------------------------------
  */
+ #define validate_argc(objc, objv) { \
+    if (objc != 2) { \
+	Tcl_WrongNumArgs(interp, 1, objv, "data"); \
+	return TCL_ERROR; \
+    } \
+}
+ 
 int DigestMD4Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    if (objc != 2) {
-	Tcl_WrongNumArgs(interp, 1, objv, "data");
-	return TCL_ERROR;
-    }
+    validate_argc(objc, objv);
     return Tls_DigestData(interp, objv[1], EVP_md4(), NULL, HEX_FORMAT | TYPE_MD, NULL);
 }
 
 int DigestMD5Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    if (objc != 2) {
-	Tcl_WrongNumArgs(interp, 1, objv, "data");
-	return TCL_ERROR;
-    }
+    validate_argc(objc, objv);
     return Tls_DigestData(interp, objv[1], EVP_md5(), NULL, HEX_FORMAT | TYPE_MD, NULL);
 }
 
 int DigestSHA1Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    if (objc != 2) {
-	Tcl_WrongNumArgs(interp, 1, objv, "data");
-	return TCL_ERROR;
-    }
+    validate_argc(objc, objv);
     return Tls_DigestData(interp, objv[1], EVP_sha1(), NULL, HEX_FORMAT | TYPE_MD, NULL);
 }
 
 int DigestSHA256Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    if (objc != 2) {
-	Tcl_WrongNumArgs(interp, 1, objv, "data");
-	return TCL_ERROR;
-    }
+    validate_argc(objc, objv);
     return Tls_DigestData(interp, objv[1], EVP_sha256(), NULL, HEX_FORMAT | TYPE_MD, NULL);
 }
 
 int DigestSHA512Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
-    if (objc != 2) {
-	Tcl_WrongNumArgs(interp, 1, objv, "data");
-	return TCL_ERROR;
-    }
+    validate_argc(objc, objv);
     return Tls_DigestData(interp, objv[1], EVP_sha512(), NULL, HEX_FORMAT | TYPE_MD, NULL);
 }
 
 /*
  *-------------------------------------------------------------------