Index: generic/tlsKey.c ================================================================== --- generic/tlsKey.c +++ generic/tlsKey.c @@ -7,46 +7,86 @@ * */ #include "tlsInt.h" #include "tclOpts.h" -#include +#include +#include + +/*******************************************************************/ + +/* + * Get cipher + */ +EVP_CIPHER *Util_GetCipher(Tcl_Interp *interp, char *name, int exist) { + EVP_CIPHER *cipher = NULL; + + if (name != NULL) { + cipher = EVP_get_cipherbyname(name); + if (cipher == NULL) { + Tcl_AppendResult(interp, "Invalid cipher: \"", name, "\"", NULL); + } + } else if (exist) { + Tcl_AppendResult(interp, "No cipher specified", NULL); + } + return cipher; +} + +/* + * Get message digest + */ +EVP_MD *Util_GetDigest(Tcl_Interp *interp, char *name, int exist) { + EVP_MD *md = NULL; + + if (name != NULL) { + md = EVP_get_digestbyname(name); + if (md == NULL) { + Tcl_AppendResult(interp, "Invalid digest: \"", name, "\"", NULL); + } + } else if (exist) { + Tcl_AppendResult(interp, "No digest specified", NULL); + } + return md; +} /*******************************************************************/ -static const char *command_opts [] = { - "-cipher", "-digest", "-hash", "-iterations", "-password", "-salt", "-size", NULL}; +/* Options for KDF commands */ + +static const char *command_opts [] = { + "-cipher", "-digest", "-hash", "-info", "-iterations", "-key", "-length", "-password", + "-salt", "-size", "-N", "-n", "-r", "-p", NULL}; enum _command_opts { - _opt_cipher, _opt_digest, _opt_hash, _opt_iter, _opt_password, _opt_salt, _opt_size + _opt_cipher, _opt_digest, _opt_hash, _opt_info, _opt_iter, _opt_key, _opt_length, + _opt_password, _opt_salt, _opt_size, _opt_N, _opt_n, _opt_r, _opt_p }; /* *------------------------------------------------------------------- * - * DeriveKey -- + * KDF_PBKDF2 -- * * PKCS5_PBKDF2_HMAC key derivation function (KDF) specified by PKCS #5. - * See RFC 6070. + * KDFs include PBKDF2 from RFC 2898/8018 and Scrypt from RFC 7914. * * 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, fn; - int iklen, ivlen, iter = PKCS5_DEFAULT_ITER; - unsigned char *passwd = NULL, *salt = NULL; - Tcl_Obj *resultObj; +static int KDF_PBKDF2(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + int pass_len = 0, salt_len = 0, fn; + int iklen, ivlen, iter = 1; + unsigned char *password = NULL, *salt = NULL; 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]; + int buf_len = (EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH)*4, dk_len = buf_len; + unsigned char tmpkeyiv[(EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH)*4]; char *cipherName = NULL, *digestName = NULL; dprintf("Called"); /* Clear errors */ @@ -58,24 +98,24 @@ 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); + memset(tmpkeyiv, 0, buf_len); /* Get options */ for (int idx = 1; idx < objc; idx++) { /* Get option */ if (Tcl_GetIndexFromObj(interp, objv[idx], command_opts, "option", 0, &fn) != TCL_OK) { return TCL_ERROR; } - /* Validate arg has value */ + /* Validate arg has a value */ if (++idx >= objc) { Tcl_AppendResult(interp, "No value for option \"", command_opts[fn], "\"", (char *) NULL); - return TCL_ERROR; - } + return TCL_ERROR; + } switch(fn) { case _opt_cipher: GET_OPT_STRING(objv[idx], cipherName, NULL); break; @@ -84,81 +124,209 @@ GET_OPT_STRING(objv[idx], digestName, NULL); break; case _opt_iter: GET_OPT_INT(objv[idx], &iter); break; + case _opt_key: case _opt_password: - GET_OPT_BYTE_ARRAY(objv[idx], passwd, &pass_len); + GET_OPT_BYTE_ARRAY(objv[idx], password, &pass_len); break; case _opt_salt: GET_OPT_BYTE_ARRAY(objv[idx], salt, &salt_len); break; + case _opt_length: case _opt_size: - GET_OPT_INT(objv[idx], &size); + GET_OPT_INT(objv[idx], &dk_len); break; } } /* Validate options */ - if (cipherName != NULL) { -#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; - } - } - if (digestName != NULL) { -#if OPENSSL_VERSION_NUMBER < 0x30000000L - md = EVP_get_digestbyname(digestName); -#else - md = EVP_MD_fetch(NULL, digestName, NULL); -#endif - if (md == NULL) { - Tcl_AppendResult(interp, "Invalid digest: \"", digestName, "\"", NULL); - return TCL_ERROR; - } - } + if (cipherName != NULL && (cipher = Util_GetCipher(interp, cipherName, 0)) == NULL) { + return TCL_ERROR; + } + + if ((md = Util_GetDigest(interp, digestName, TRUE)) == NULL) { + return TCL_ERROR; + } + 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)); + + if (dk_len < 1 || dk_len > buf_len) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid derived key length %d: must be 0 < size <= %d", dk_len, buf_len)); return TCL_ERROR; } + /* Set output type sizes */ if (cipher == NULL) { - if (size > max) size = max; - iklen = size; + if (dk_len > buf_len) dk_len = buf_len; + iklen = dk_len; ivlen = 0; } else { iklen = EVP_CIPHER_key_length(cipher); ivlen = EVP_CIPHER_iv_length(cipher); - size = iklen+ivlen; + dk_len = iklen+ivlen; } - /* Perform password derivation */ - if (!PKCS5_PBKDF2_HMAC(passwd, pass_len, salt, salt_len, iter, md, size, tmpkeyiv)) { + /* Derive key */ + if (!PKCS5_PBKDF2_HMAC(password, pass_len, salt, salt_len, iter, md, dk_len, tmpkeyiv)) { Tcl_AppendResult(interp, "Key derivation failed: ", REASON(), NULL); return TCL_ERROR; } - /* Return key and iv */ + /* Set result to key and iv */ if (cipher == NULL) { - resultObj = Tcl_NewByteArrayObj(tmpkeyiv, size); + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(tmpkeyiv, dk_len)); } else { - resultObj = Tcl_NewListObj(0, NULL); + Tcl_Obj *resultObj = Tcl_NewListObj(0, NULL); LAPPEND_BARRAY(interp, resultObj, "key", tmpkeyiv, iklen); LAPPEND_BARRAY(interp, resultObj, "iv", tmpkeyiv+iklen, ivlen); + Tcl_SetObjResult(interp, resultObj); } - Tcl_SetObjResult(interp, resultObj); + + /* Clear data */ + memset(tmpkeyiv, 0, buf_len); return TCL_OK; - clientData = clientData; +} + +/* + *------------------------------------------------------------------- + * + * KDF_HKDF -- + * + * HMAC-based Extract-and-Expand Key Derivation Function (HKDF). + * See RFC 5869. + * + * Returns: + * TCL_OK or TCL_ERROR + * + * Side effects: + * Sets result to a key of specified length, or an error message + * + *------------------------------------------------------------------- + */ +static int KDF_HKDF(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { + EVP_PKEY_CTX *pctx; + const EVP_MD *md = NULL; + unsigned char *salt = NULL, *key = NULL, *info = NULL, *out = NULL; + int salt_len = 0, key_len = 0, info_len = 0, dk_len = 1024, res = TCL_OK, fn; + char *digestName; + size_t out_len; + Tcl_Obj *resultObj; + + dprintf("Called"); + + /* Clear errors */ + Tcl_ResetResult(interp); + ERR_clear_error(); + + /* Validate arg count */ + if (objc < 3 || objc > 11) { + Tcl_WrongNumArgs(interp, 1, objv, "-digest digest -key string ?-info string? ?-salt string? ?-size derived_length?"); + return TCL_ERROR; + } + + /* Get options */ + for (int idx = 1; idx < objc; idx++) { + /* Get option */ + if (Tcl_GetIndexFromObj(interp, objv[idx], command_opts, "option", 0, &fn) != TCL_OK) { + return TCL_ERROR; + } + + /* Validate arg has a value */ + if (++idx >= objc) { + Tcl_AppendResult(interp, "No value for option \"", command_opts[fn], "\"", (char *) NULL); + return TCL_ERROR; + } + + switch(fn) { + case _opt_digest: + case _opt_hash: + GET_OPT_STRING(objv[idx], digestName, NULL); + break; + case _opt_info: + /* Max 1024/2048 */ + GET_OPT_BYTE_ARRAY(objv[idx], info, &info_len); + break; + case _opt_key: + case _opt_password: + GET_OPT_BYTE_ARRAY(objv[idx], key, &key_len); + break; + case _opt_salt: + GET_OPT_BYTE_ARRAY(objv[idx], salt, &salt_len); + break; + case _opt_length: + case _opt_size: + GET_OPT_INT(objv[idx], &dk_len); + break; + } + } + + /* Get digest */ + if ((md = Util_GetDigest(interp, digestName, TRUE)) == NULL) { + goto error; + } + + /* Create context */ + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL); + goto error; + } + + if (EVP_PKEY_derive_init(pctx) < 1) { + Tcl_AppendResult(interp, "Initialize failed: ", REASON(), NULL); + goto error; + } + + /* Set config parameters */ + if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) < 1) { + Tcl_AppendResult(interp, "Set digest failed: ", REASON(), NULL); + goto error; + } + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, key, key_len) < 1) { + Tcl_AppendResult(interp, "Set key failed: ", REASON(), NULL); + goto error; + } + if (salt != NULL && EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) < 1) { + Tcl_AppendResult(interp, "Set salt failed: ", REASON(), NULL); + goto error; + } + if (info != NULL && EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) < 1) { + Tcl_AppendResult(interp, "Set info failed: ", REASON(), NULL); + goto error; + } + + /* Get buffer */ + resultObj = Tcl_NewObj(); + if ((out = Tcl_SetByteArrayLength(resultObj, dk_len)) == NULL) { + Tcl_AppendResult(interp, "Memory allocation error", (char *) NULL); + goto error; + } + out_len = (size_t) dk_len; + + /* Derive key */ + if (EVP_PKEY_derive(pctx, out, &out_len) > 0) { + /* Shrink buffer to actual size */ + Tcl_SetByteArrayLength(resultObj, (int) out_len); + Tcl_SetObjResult(interp, resultObj); + goto done; + } else { + Tcl_AppendResult(interp, "Derive key failed: ", REASON(), NULL); + Tcl_DecrRefCount(resultObj); + } + +error: + res = TCL_ERROR; +done: + if (pctx != NULL) { + EVP_PKEY_CTX_free(pctx); + } + return TCL_OK; } /* *------------------------------------------------------------------- * @@ -173,9 +341,10 @@ * Creates commands * *------------------------------------------------------------------- */ int Tls_KeyCommands(Tcl_Interp *interp) { - Tcl_CreateObjCommand(interp, "tls::derive_key", DeriveKey, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::hkdf", KDF_HKDF, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "tls::pbkdf2", KDF_PBKDF2, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }