Index: configure ================================================================== --- configure +++ configure @@ -5394,11 +5394,11 @@ # This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS # and PKG_TCL_SOURCES. #----------------------------------------------------------------------- - vars="tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsX509.c" + vars="tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsKey.c tlsX509.c" for i in $vars; do case $i in \$*) # allow $-var names PKG_SOURCES="$PKG_SOURCES $i" Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -69,11 +69,11 @@ # and runtime Tcl library files in TEA_ADD_TCL_SOURCES. # This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS # and PKG_TCL_SOURCES. #----------------------------------------------------------------------- -TEA_ADD_SOURCES([tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsX509.c]) +TEA_ADD_SOURCES([tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsKey.c tlsX509.c]) TEA_ADD_HEADERS([generic/tls.h]) TEA_ADD_INCLUDES([]) TEA_ADD_LIBS([]) TEA_ADD_CFLAGS([]) TEA_ADD_STUB_SOURCES([]) Index: doc/cryptography.html ================================================================== --- doc/cryptography.html +++ doc/cryptography.html @@ -38,10 +38,12 @@ <dd><b>tls::sha512</b> <em>data</em></dd> <dd><b>tls::unstack</b> <em>channelId</em></dd> <dt> </dt> <dd><b>tls::encrypt</b> <b>-cipher</b> <em>name</em> <b>-key</b> <em>key ?options?</em></dd> <dd><b>tls::decrypt</b> <b>-cipher</b> <em>name</em> <b>-key</b> <em>key ?options?</em></dd> + <dt> </dt> + <dd><b>tls::derive_key</b> <em>key ?options?</em></dd> </dl> </dd> <dd><a href="#OPTIONS">OPTIONS</a></dd> <dd><a href="#COMMANDS">COMMANDS</a></dd> <dd><a href="#GLOSSARY">GLOSSARY</a> </dd> @@ -85,10 +87,12 @@ <a href="#tls::sha512"><b>tls::sha512</b> <i>data</i></a><br> <a href="#tls::unstack"><b>tls::unstack</b> <i>channelId</i></a><br> <br> <a href="#tls::encrypt"><b>tls::encrypt</b> <b>-cipher</b> <i>name</i> <b>-key</b> <i>key ?options?</i></a><br> <a href="#tls::decrypt"><b>tls::decrypt</b> <b>-cipher</b> <i>name</i> <b>-key</b> <i>key ?options?</i></a><br> +<br> +<a href="#tls::derive_key"><b>tls::derive_key</b> <i>?options?</i></a><br> </p> <br> <h3><a name="OPTIONS">OPTIONS</a></h3> @@ -111,31 +115,31 @@ See <a href="#tls::digests"><b>tls::digests</b></a> for the valid values.</dd> </dl> <dl> <dt><a name="-iterations"><strong>-iterations</strong> <em>count</em></a></dt> - <dd>Number (integer) of iterations on the password to use in deriving the - encryption key. Default is 10000. Some KDF implementations require an - iteration count.</dd> + <dd>Number (integer > 0) of iterations to use in deriving the encryption + key. Default is 2048. Some <a href="#KDF"><b>KDF</b></a> implementations + require an iteration count.</dd> </dl> <dl> <dt><a name="-iv"><strong>-iv</strong> <em>string</em></a></dt> <dd>Initialization vector (IV) to use. Required for some ciphers and GMAC. - Cipher modes CBC, CFB, OFB and CTR all need an IV while ECB mode does not. + Cipher modes CBC, CFB, and OFB all need an IV while ECB and CTR modes do not. A new, random IV should be created for each use. Think of the IV as a nonce (number used once), it's public but random and unpredictable. See the <a href="#tls::cipher"><b>tls::cipher</b></a> for iv_length and - when required (length > 0). If not set, it will default to \x00 fill data.</dd> + when required (length > 0). Max is 16 bytes. If not set, it will default to \x00 fill data.</dd> </dl> <dl> <dt><a name="-key"><strong>-key</strong> <em>string</em></a></dt> <dd>Encryption key to use for cryptography function. Can be a binary or text string. Longer keys provide better protection. Used by ciphers, HMAC, some CMAC, and some KDF implementations. If the length of the key is < - <b>key_length</b> it will be padded. If > key_length, it will be rejected. + <b>key_length</b> it will be padded. Max is 64 bytes. If > key_length, it will be rejected. See the <a href="#tls::cipher"><b>tls::cipher</b></a> for key_length.</dd> </dl> <dl> <dt><a name="-mac"><strong>-mac</strong> <em>name</em></a></dt> @@ -143,23 +147,25 @@ See <a href="#tls::mac"><b>tls::macs</b></a> for the valid values.</dd> </dl> <dl> <dt><a name="-password"><strong>-password</strong> <em>string</em></a></dt> - <dd>Password to use for some KDF functions.</dd> + <dd>Password to use for some KDF functions. If not specified, the default + value is used. Can be a binary or text string.</dd> </dl> <dl> <dt><a name="-properties"><strong>-properties</strong> <em>list</em></a></dt> - <dd>List of additional properties to pass to cryptography function.</dd> + <dd>List of additional properties to pass to cryptographic function.</dd> </dl> <dl> <dt><a name="-salt"><strong>-salt</strong> <em>string</em></a></dt> - <dd>Specifies salt value to use when encrypting data. Default is to use a - randomly generated value. This option is used by BLAKE2 MAC and some KDF - implementations use a non-secret unique cryptographic salt.</dd> + <dd>Specifies salt value to use when encrypting data. Can be a binary or + text string. Default is to use a randomly generated value. This option is + used by BLAKE2 MAC and some KDF implementations use a non-secret unique + cryptographic salt.</dd> </dl> <dl> <dt><a name="-size"><strong>-size</strong> <em>number</em></a></dt> <dd>Set the output hash size in bytes. Used by KMAC128 or KMAC256 to specify @@ -282,11 +288,11 @@ <p>The following commands provide access to the OpenSSL cryptography functions.</p> <dl> -<h4>Info Commands</h4> +<h4><a name="Info">Info Commands</a></h4> <dt><a name="tls::cipher"><strong>tls::cipher</strong> <em>name</em></a></dt> <dd>Return a list of property names and values describing cipher <i>name</i>. Properties include name, description, block_size, key_length, iv_length, type, and mode list. If block-size is 1, @@ -327,11 +333,11 @@ <dt><a name="tls::version"><strong>tls::version</strong></a></dt> <dd>Returns the OpenSSL version string.</dd> <br> -<h4>Message Digest (MD) and Message Authentication Code (MAC) Commands</h4> +<h4><a name="MD_MAC">Message Digest (MD) and Message Authentication Code (MAC) Commands</a></h4> <dt><a name="tls::cmac"><strong>tls::cmac</strong> <em>?</em><b>-cipher</b><em>? name</em> <b>-key</b> <em>key ?</em><b>-bin</b>|<b>-hex</b><em>? [</em><b>-chan</b> <em>channelId |</em> <b>-command</b> <em>cmdName |</em> @@ -392,11 +398,11 @@ <dt><a name="tls::unstack"><strong>tls::unstack</strong> <em>channelId</em></a></dt> <dd>Removes the top level cryptographic transform from channel <em>channelId</em>.</dd> <br> -<h4>Encryption and Decryption Commands</h4> +<h4><a name="Cipher">Encryption and Decryption Commands</a></h4> <dt><a name="tls::encrypt"><strong>tls::encrypt</strong> <em>?</em><b>-cipher</b><em>? name</em> <b>-key</b> <em>key ?</em><b>-iv</b> <em>string? [</em><b>-chan</b> <em>channelId |</em> <b>-command</b> <em>cmdName |</em> <b>-infile</b> <em>filename</em> <b>-outfile</b> <em>filename |</em> @@ -419,10 +425,27 @@ info. Option <b>-iv</b> is only used for some ciphers. See the "<b>tls::cipher</b> <em>cipher</em>" command for key and iv sizes and when the iv is used (iv_length > 0).</dd> </dl> +<br> + +<h4><a name="KDF">Key Derivation Function (KDF) Commands</a></h4> + + <dt><a name="tls::derive_key"><strong>tls::derive_key</strong> + <em>[</em><b>-cipher</b> <em>cipher |</em> <b>-size</b> <em>size]</em> + <b>-digest</b> <em>digest ?</em><b>-iterations</b> <em>count? + ?</em><b>-password</b> <em>string? ?</em><b>-salt</b> <em>string?</em></a></dt> + <dd>Derive a key and initialization vector (iv) from a password and salt + value using PKCS5_PBKDF2_HMAC. This is a more secure way to generate + keys and ivs for use by <a href="#tls::encrypt"><b>tls::encrypt</b></a>. + See <a href="#OPTIONS"><b>options</b></a> for usage info. If <b>-cipher</b> + is specified, then the derived key and iv sized for that cipher are + returned as a key-value list. If not or if <b>-size</b> is specified, + then the derived key (dk) of <em>size</em> bytes is returned.</dd> +</dl> + <br> <h3><a name="GLOSSARY">GLOSSARY</a></h3> <p>The following is a list of the terminology used in this package along with brief definitions. For more details, please consult with the OpenSSL documentation.</p> Index: generic/tls.c ================================================================== --- generic/tls.c +++ generic/tls.c @@ -2550,10 +2550,11 @@ Tcl_CreateObjCommand(interp, "tls::status", StatusObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); Tls_DigestCommands(interp); Tls_EncryptCommands(interp); Tls_InfoCommands(interp); + Tls_KeyCommands(interp); if (interp) { Tcl_Eval(interp, tlsTclInitScript); } Index: generic/tlsDigest.c ================================================================== --- generic/tlsDigest.c +++ generic/tlsDigest.c @@ -1239,10 +1239,11 @@ OPTOBJ("-command", cmdObj); OPTOBJ("-data", dataObj); OPTOBJ("-digest", digestObj); OPTOBJ("-file", fileObj); OPTOBJ("-filename", fileObj); + OPTOBJ("-hash", digestObj); OPTOBJ("-key", keyObj); OPTOBJ("-mac", macObj); OPTBAD("option", "-bin, -channel, -cipher, -command, -data, -digest, -file, -filename, -hex, -key, or -mac"); return TCL_ERROR; Index: generic/tlsEncrypt.c ================================================================== --- generic/tlsEncrypt.c +++ generic/tlsEncrypt.c @@ -1248,10 +1248,11 @@ OPTSTR("-channel", channel); OPTOBJ("-cipher", cipherObj); OPTOBJ("-command", cmdObj); OPTOBJ("-data", dataObj); OPTOBJ("-digest", digestObj); + OPTOBJ("-hash", digestObj); OPTOBJ("-infile", inFileObj); OPTOBJ("-outfile", outFileObj); OPTOBJ("-key", keyObj); OPTOBJ("-iv", ivObj); OPTOBJ("-mac", macObj); Index: generic/tlsInt.h ================================================================== --- generic/tlsInt.h +++ generic/tlsInt.h @@ -35,11 +35,10 @@ # define CONST86 const # else # define CONST86 # endif #endif - /* * Backwards compatibility for size type change */ #if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7 # define Tcl_Size int @@ -196,11 +195,12 @@ void Tls_Clean(State *statePtr); int Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent); int Tls_DigestCommands(Tcl_Interp *interp); int Tls_EncryptCommands(Tcl_Interp *interp); int Tls_InfoCommands(Tcl_Interp *interp); +int Tls_KeyCommands(Tcl_Interp *interp); BIO *BIO_new_tcl(State* statePtr, int flags); #define PTR2INT(x) ((int) ((intptr_t) (x))) #endif /* _TLSINT_H */ ADDED generic/tlsKey.c Index: generic/tlsKey.c ================================================================== --- /dev/null +++ generic/tlsKey.c @@ -0,0 +1,152 @@ +/* + * Key Derivation Function (KDF) Module + * + * Provides commands to derive keys. + * + * Copyright (C) 2023 Brian O'Hagan + * + */ + +#include "tlsInt.h" +#include "tclOpts.h" +#include <openssl/crypto.h> + +/*******************************************************************/ + +/* + *------------------------------------------------------------------- + * + * DeriveKey -- + * + * PKCS5_PBKDF2_HMAC key derivation function (KDF) specified by PKCS #5. + * See RFC 6070. + * + * Returns: + * TCL_OK or TCL_ERROR + * + * Side effects: + * Sets result to a list of key and iv values, or an error message + * + *------------------------------------------------------------------- + */ +static int DeriveKey(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + int key_len = 0, md_len = 0, pass_len = 0, salt_len = 0; + int iklen, ivlen, iter = PKCS5_DEFAULT_ITER; + unsigned char *passwd = NULL, *salt = NULL; + Tcl_Obj *cipherObj = NULL, *digestObj = NULL, *passwdObj = NULL, *saltObj = NULL, *resultObj; + const EVP_MD *md = NULL; + const EVP_CIPHER *cipher = NULL; + int max = EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH, size = max; + unsigned char tmpkeyiv[EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH]; + + dprintf("Called"); + + /* Clear errors */ + Tcl_ResetResult(interp); + ERR_clear_error(); + + /* Validate arg count */ + if (objc < 3 || objc > 11) { + Tcl_WrongNumArgs(interp, 1, objv, "[-cipher cipher | -size length] -digest digest ?-iterations count? ?-password string? ?-salt string?"); + return TCL_ERROR; + } + + /* Init buffers */ + memset(tmpkeyiv, 0, EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH); + + /* Get options */ + for (int idx = 1; idx < objc; idx++) { + char *opt = Tcl_GetStringFromObj(objv[idx], NULL); + + if (opt[0] != '-') { + break; + } + + OPTOBJ("-cipher", cipherObj); + OPTOBJ("-digest", digestObj); + OPTOBJ("-hash", digestObj); + OPTINT("-iterations", iter); + OPTOBJ("-password", passwdObj); + OPTOBJ("-salt", saltObj); + OPTINT("-size", size); + + OPTBAD("option", "-cipher, -digest, -iterations, -password, -salt, or -size option"); + return TCL_ERROR; + } + + /* Validate options */ + if (cipherObj != NULL) { + char *name = Tcl_GetByteArrayFromObj(cipherObj, NULL); + cipher = EVP_get_cipherbyname(name); + } + if (digestObj != NULL) { + char *name = Tcl_GetStringFromObj(digestObj, &md_len); + md = EVP_get_digestbyname(name); + } else { + Tcl_AppendResult(interp, "No digest specified", NULL); + return TCL_ERROR; + } + if (passwdObj != NULL) { + passwd = Tcl_GetByteArrayFromObj(passwdObj, &pass_len); + } + if (saltObj != NULL) { + salt = Tcl_GetByteArrayFromObj(saltObj, &salt_len); + } + if (iter < 1) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid iterations count %d: must be > 0", iter)); + return TCL_ERROR; + } + if (size < 1 || size > max) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid derived key length %d: must be 0 < size <= %d", size, max)); + return TCL_ERROR; + } + + if (cipher == NULL) { + if (size > max) size = max; + iklen = size; + ivlen = 0; + } else { + iklen = EVP_CIPHER_key_length(cipher); + ivlen = EVP_CIPHER_iv_length(cipher); + size = iklen+ivlen; + } + + /* Perform password derivation */ + if (!PKCS5_PBKDF2_HMAC(passwd, pass_len, salt, salt_len, iter, md, size, tmpkeyiv)) { + Tcl_AppendResult(interp, "Key derivation failed: ", REASON(), NULL); + return TCL_ERROR; + } + + /* Return key and iv */ + if (cipher == NULL) { + resultObj = Tcl_NewByteArrayObj(tmpkeyiv, size); + } else { + resultObj = Tcl_NewListObj(0, NULL); + LAPPEND_BARRAY(interp, resultObj, "key", tmpkeyiv, iklen); + LAPPEND_BARRAY(interp, resultObj, "iv", tmpkeyiv+iklen, ivlen); + } + Tcl_SetObjResult(interp, resultObj); + return TCL_OK; + clientData = clientData; +} + +/* + *------------------------------------------------------------------- + * + * Tls_KeyCommands -- + * + * Create key commands + * + * Returns: + * TCL_OK or TCL_ERROR + * + * Side effects: + * Creates commands + * + *------------------------------------------------------------------- + */ +int Tls_KeyCommands(Tcl_Interp *interp) { + Tcl_CreateObjCommand(interp, "tls::derive_key", DeriveKey, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + return TCL_OK; +} + Index: win/makefile.vc ================================================================== --- win/makefile.vc +++ win/makefile.vc @@ -29,10 +29,11 @@ $(TMP_DIR)\tlsBIO.obj \ $(TMP_DIR)\tlsDigest.obj \ $(TMP_DIR)\tlsEncrypt.obj \ $(TMP_DIR)\tlsInfo.obj \ $(TMP_DIR)\tlsIO.obj \ + $(TMP_DIR)\tlsKey.obj \ $(TMP_DIR)\tlsX509.obj # Define any additional project include flags # SSL_INSTALL_FOLDER = with the OpenSSL installation folder following. PRJ_INCLUDES = -I"$(SSL_INSTALL_FOLDER)\include" -I"$(OPENSSL_INSTALL_DIR)\include"