Some BIG tweaks to ENGINE code.
This change adds some new functionality to the ENGINE code and API to make it possible for ENGINEs to describe and implement their own control commands that can be interrogated and used by calling applications at run-time. The source code includes numerous comments explaining how it all works and some of the finer details. But basically, an ENGINE will normally declare an array of ENGINE_CMD_DEFN entries in its ENGINE - and the various new ENGINE_CTRL_*** command types take care of iterating through this list of definitions, converting command numbers to names, command names to numbers, getting descriptions, getting input flags, etc. These administrative commands are handled directly in the base ENGINE code rather than in each ENGINE's ctrl() handler, unless they specify the ENGINE_FLAGS_MANUAL_CMD_CTRL flag (ie. if they're doing something clever or dynamic with the command definitions). There is also a new function, ENGINE_cmd_is_executable(), that will determine if an ENGINE control command is of an "executable" type that can be used in another new function, ENGINE_ctrl_cmd_string(). If not, the control command is not supposed to be exposed out to user/config level access - eg. it could involve the exchange of binary data, returning results to calling code, etc etc. If the command is executable then ENGINE_ctrl_cmd_string() can be called using a name/arg string pair. The control command's input flags will be used to determine necessary conversions before the control command is called, and commands of this form will always return zero or one (failure or success, respectively). This is set up so that arbitrary applications can support control commands in a consistent way so that tweaking particular ENGINE behaviour is specific to the ENGINE and the host environment, and independant of the application or OpenSSL. Some code demonstrating this stuff in action will applied shortly to the various ENGINE implementations, as well as "openssl engine" support for executing arbitrary control commands before and/or after initialising various ENGINEs.
This commit is contained in:
@@ -77,6 +77,10 @@ static ENGINE *engine_def_bn_mod_exp_crt = NULL;
|
||||
* initialised to suitable start-up defaults. */
|
||||
static int engine_def_flag = 0;
|
||||
|
||||
/* When querying a ENGINE-specific control command's 'description', this string
|
||||
* is used if the ENGINE_CMD_DEFN has cmd_desc set to NULL. */
|
||||
static const char *int_no_description = "";
|
||||
|
||||
/* This is used in certain static utility functions to save code
|
||||
* repetition for per-algorithm functions. */
|
||||
typedef enum {
|
||||
@@ -288,6 +292,121 @@ EVP_PKEY *ENGINE_load_public_key(ENGINE *e, const char *key_id,
|
||||
return pkey;
|
||||
}
|
||||
|
||||
/* These internal functions handle 'CMD'-related control commands when the
|
||||
* ENGINE in question has asked us to take care of it (ie. the ENGINE did not
|
||||
* set the ENGINE_FLAGS_MANUAL_CMD_CTRL flag. */
|
||||
|
||||
static int int_ctrl_cmd_is_null(const ENGINE_CMD_DEFN *defn)
|
||||
{
|
||||
if((defn->cmd_num == 0) || (defn->cmd_name == NULL))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int int_ctrl_cmd_by_name(const ENGINE_CMD_DEFN *defn, const char *s)
|
||||
{
|
||||
int idx = 0;
|
||||
while(!int_ctrl_cmd_is_null(defn) && (strcmp(defn->cmd_name, s) != 0))
|
||||
{
|
||||
idx++;
|
||||
defn++;
|
||||
}
|
||||
if(int_ctrl_cmd_is_null(defn))
|
||||
/* The given name wasn't found */
|
||||
return -1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
static int int_ctrl_cmd_by_num(const ENGINE_CMD_DEFN *defn, unsigned int num)
|
||||
{
|
||||
int idx = 0;
|
||||
/* NB: It is stipulated that 'cmd_defn' lists are ordered by cmd_num. So
|
||||
* our searches don't need to take any longer than necessary. */
|
||||
while(!int_ctrl_cmd_is_null(defn) && (defn->cmd_num < num))
|
||||
{
|
||||
idx++;
|
||||
defn++;
|
||||
}
|
||||
if(defn->cmd_num == num)
|
||||
return idx;
|
||||
/* The given cmd_num wasn't found */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int int_ctrl_helper(ENGINE *e, int cmd, long i, void *p, void (*f)())
|
||||
{
|
||||
int idx;
|
||||
char *s = (char *)p;
|
||||
/* Take care of the easy one first (eg. it requires no searches) */
|
||||
if(cmd == ENGINE_CTRL_GET_FIRST_CMD_TYPE)
|
||||
{
|
||||
if((e->cmd_defns == NULL) || int_ctrl_cmd_is_null(e->cmd_defns))
|
||||
return 0;
|
||||
return e->cmd_defns->cmd_num;
|
||||
}
|
||||
/* One or two commands require that "p" be a valid string buffer */
|
||||
if((cmd == ENGINE_CTRL_GET_CMD_FROM_NAME) ||
|
||||
(cmd == ENGINE_CTRL_GET_NAME_FROM_CMD) ||
|
||||
(cmd == ENGINE_CTRL_GET_DESC_FROM_CMD))
|
||||
{
|
||||
if(s == NULL)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_INT_CTRL_HELPER,
|
||||
ERR_R_PASSED_NULL_PARAMETER);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/* Now handle cmd_name -> cmd_num conversion */
|
||||
if(cmd == ENGINE_CTRL_GET_CMD_FROM_NAME)
|
||||
{
|
||||
if((e->cmd_defns == NULL) || ((idx = int_ctrl_cmd_by_name(
|
||||
e->cmd_defns, s)) < 0))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_INT_CTRL_HELPER,
|
||||
ENGINE_R_INVALID_CMD_NAME);
|
||||
return -1;
|
||||
}
|
||||
return e->cmd_defns[idx].cmd_num;
|
||||
}
|
||||
/* For the rest of the commands, the 'long' argument must specify a
|
||||
* valie command number - so we need to conduct a search. */
|
||||
if((e->cmd_defns == NULL) || ((idx = int_ctrl_cmd_by_num(e->cmd_defns,
|
||||
(unsigned int)i)) < 0))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_INT_CTRL_HELPER,
|
||||
ENGINE_R_INVALID_CMD_NUMBER);
|
||||
return -1;
|
||||
}
|
||||
/* Now the logic splits depending on command type */
|
||||
switch(cmd)
|
||||
{
|
||||
case ENGINE_CTRL_GET_NEXT_CMD_TYPE:
|
||||
idx++;
|
||||
if(int_ctrl_cmd_is_null(e->cmd_defns + idx))
|
||||
/* end-of-list */
|
||||
return 0;
|
||||
else
|
||||
return e->cmd_defns[idx].cmd_num;
|
||||
case ENGINE_CTRL_GET_NAME_LEN_FROM_CMD:
|
||||
return strlen(e->cmd_defns[idx].cmd_name);
|
||||
case ENGINE_CTRL_GET_NAME_FROM_CMD:
|
||||
return sprintf(s, "%s", e->cmd_defns[idx].cmd_name);
|
||||
case ENGINE_CTRL_GET_DESC_LEN_FROM_CMD:
|
||||
if(e->cmd_defns[idx].cmd_desc)
|
||||
return strlen(e->cmd_defns[idx].cmd_desc);
|
||||
return strlen(int_no_description);
|
||||
case ENGINE_CTRL_GET_DESC_FROM_CMD:
|
||||
if(e->cmd_defns[idx].cmd_desc)
|
||||
return sprintf(s, "%s", e->cmd_defns[idx].cmd_desc);
|
||||
return sprintf(s, "%s", int_no_description);
|
||||
case ENGINE_CTRL_GET_CMD_FLAGS:
|
||||
return e->cmd_defns[idx].cmd_flags;
|
||||
}
|
||||
/* Shouldn't really be here ... */
|
||||
ENGINEerr(ENGINE_F_INT_CTRL_HELPER,ENGINE_R_INTERNAL_LIST_ERROR);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)())
|
||||
{
|
||||
int ctrl_exists, ref_exists;
|
||||
@@ -298,14 +417,42 @@ int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)())
|
||||
}
|
||||
CRYPTO_w_lock(CRYPTO_LOCK_ENGINE);
|
||||
ref_exists = ((e->struct_ref > 0) ? 1 : 0);
|
||||
ctrl_exists = (e->ctrl ? 1 : 0);
|
||||
CRYPTO_w_unlock(CRYPTO_LOCK_ENGINE);
|
||||
ctrl_exists = ((e->ctrl == NULL) ? 0 : 1);
|
||||
if(!ref_exists)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL,ENGINE_R_NO_REFERENCE);
|
||||
return 0;
|
||||
}
|
||||
if (!ctrl_exists)
|
||||
/* Intercept any "root-level" commands before trying to hand them on to
|
||||
* ctrl() handlers. */
|
||||
switch(cmd)
|
||||
{
|
||||
case ENGINE_CTRL_HAS_CTRL_FUNCTION:
|
||||
return ctrl_exists;
|
||||
case ENGINE_CTRL_GET_FIRST_CMD_TYPE:
|
||||
case ENGINE_CTRL_GET_NEXT_CMD_TYPE:
|
||||
case ENGINE_CTRL_GET_CMD_FROM_NAME:
|
||||
case ENGINE_CTRL_GET_NAME_LEN_FROM_CMD:
|
||||
case ENGINE_CTRL_GET_NAME_FROM_CMD:
|
||||
case ENGINE_CTRL_GET_DESC_LEN_FROM_CMD:
|
||||
case ENGINE_CTRL_GET_DESC_FROM_CMD:
|
||||
case ENGINE_CTRL_GET_CMD_FLAGS:
|
||||
if(ctrl_exists && !(e->flags & ENGINE_FLAGS_MANUAL_CMD_CTRL))
|
||||
return int_ctrl_helper(e,cmd,i,p,f);
|
||||
if(!ctrl_exists)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL,ENGINE_R_NO_CONTROL_FUNCTION);
|
||||
/* For these cmd-related functions, failure is indicated
|
||||
* by a -1 return value (because 0 is used as a valid
|
||||
* return in some places). */
|
||||
return -1;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* Anything else requires a ctrl() handler to exist. */
|
||||
if(!ctrl_exists)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL,ENGINE_R_NO_CONTROL_FUNCTION);
|
||||
return 0;
|
||||
@@ -313,6 +460,125 @@ int ENGINE_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f)())
|
||||
return e->ctrl(e, cmd, i, p, f);
|
||||
}
|
||||
|
||||
int ENGINE_cmd_is_executable(ENGINE *e, int cmd)
|
||||
{
|
||||
int flags;
|
||||
if((flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, cmd, NULL, NULL)) < 0)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CMD_IS_EXECUTABLE,
|
||||
ENGINE_R_INVALID_CMD_NUMBER);
|
||||
return 0;
|
||||
}
|
||||
if(!(flags & ENGINE_CMD_FLAG_NO_INPUT) &&
|
||||
!(flags & ENGINE_CMD_FLAG_NUMERIC) &&
|
||||
!(flags & ENGINE_CMD_FLAG_STRING))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ENGINE_ctrl_cmd_string(ENGINE *e, const char *cmd_name, const char *arg,
|
||||
int cmd_optional)
|
||||
{
|
||||
int num, flags;
|
||||
long l;
|
||||
char *ptr;
|
||||
if((e == NULL) || (cmd_name == NULL))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ERR_R_PASSED_NULL_PARAMETER);
|
||||
return 0;
|
||||
}
|
||||
if((e->ctrl == NULL) || ((num = ENGINE_ctrl(e,
|
||||
ENGINE_CTRL_GET_CMD_FROM_NAME,
|
||||
0, (void *)cmd_name, NULL)) <= 0))
|
||||
{
|
||||
/* If the command didn't *have* to be supported, we fake
|
||||
* success. This allows certain settings to be specified for
|
||||
* multiple ENGINEs and only require a change of ENGINE id
|
||||
* (without having to selectively apply settings). Eg. changing
|
||||
* from a hardware device back to the regular software ENGINE
|
||||
* without editing the config file, etc. */
|
||||
if(cmd_optional)
|
||||
{
|
||||
ERR_clear_error();
|
||||
return 1;
|
||||
}
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_INVALID_CMD_NAME);
|
||||
return 0;
|
||||
}
|
||||
if(!ENGINE_cmd_is_executable(e, num))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_CMD_NOT_EXECUTABLE);
|
||||
return 0;
|
||||
}
|
||||
if((flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, num, NULL, NULL)) < 0)
|
||||
{
|
||||
/* Shouldn't happen, given that ENGINE_cmd_is_executable()
|
||||
* returned success. */
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_INTERNAL_LIST_ERROR);
|
||||
return 0;
|
||||
}
|
||||
/* If the command takes no input, there must be no input. And vice
|
||||
* versa. */
|
||||
if(flags & ENGINE_CMD_FLAG_NO_INPUT)
|
||||
{
|
||||
if(arg != NULL)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_COMMAND_TAKES_NO_INPUT);
|
||||
return 0;
|
||||
}
|
||||
/* We deliberately force the result of ENGINE_ctrl() to 0 or 1
|
||||
* rather than returning it as "return data". This is to ensure
|
||||
* usage of these commands is consistent across applications and
|
||||
* that certain applications don't understand it one way, and
|
||||
* others another. */
|
||||
if(ENGINE_ctrl(e, num, 0, (void *)arg, NULL))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
/* So, we require input */
|
||||
if(arg == NULL)
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_COMMAND_TAKES_INPUT);
|
||||
return 0;
|
||||
}
|
||||
/* If it takes string input, that's easy */
|
||||
if(flags & ENGINE_CMD_FLAG_STRING)
|
||||
{
|
||||
/* Same explanation as above */
|
||||
if(ENGINE_ctrl(e, num, 0, (void *)arg, NULL))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
/* If it doesn't take numeric either, then it is unsupported for use in
|
||||
* a config-setting situation, which is what this function is for. This
|
||||
* should never happen though, because ENGINE_cmd_is_executable() was
|
||||
* used. */
|
||||
if(!(flags & ENGINE_CMD_FLAG_NUMERIC))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_INTERNAL_LIST_ERROR);
|
||||
return 0;
|
||||
}
|
||||
l = strtol(arg, &ptr, 10);
|
||||
if((arg == ptr) || (*ptr != '\0'))
|
||||
{
|
||||
ENGINEerr(ENGINE_F_ENGINE_CTRL_CMD_STRING,
|
||||
ENGINE_R_ARGUMENT_IS_NOT_A_NUMBER);
|
||||
return 0;
|
||||
}
|
||||
/* Force the result of the control command to 0 or 1, for the reasons
|
||||
* mentioned before. */
|
||||
if(ENGINE_ctrl(e, num, l, NULL, NULL))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ENGINE *engine_get_default_type(ENGINE_TYPE t)
|
||||
{
|
||||
ENGINE *ret = NULL;
|
||||
|
||||
Reference in New Issue
Block a user