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); } /* *-------------------------------------------------------------------