There is a mechanism supported by the ENGINE framework that allows each ENGINE implementation to define an arbitrary set of configuration "commands" and expose them to OpenSSL and any applications based on OpenSSL. This mechanism is entirely based on the use of name-value pairs and assumes ASCII input (no unicode or UTF for now!), so it is ideal if applications want to provide a transparent way for users to provide arbitrary configuration "directives" directly to such ENGINEs. It is also possible for the application to dynamically interrogate the loaded ENGINE implementations for the names, descriptions, and input flags of their available "control commands", providing a more flexible configuration scheme. However, if the user is expected to know which ENGINE device he/she is using (in the case of specialised hardware, this goes without saying) then applications may not need to concern themselves with discovering the supported control commands and simply prefer to pass settings into ENGINEs exactly as they are provided by the user.
Before illustrating how control commands work, it is worth mentioning what they are typically used for. Broadly speaking there are two uses for control commands; the first is to provide the necessary details to the implementation (which may know nothing at all specific to the host system) so that it can be initialised for use. This could include the path to any driver or config files it needs to load, required network addresses, smart-card identifiers, passwords to initialise protected devices, logging information, etc etc. This class of commands typically needs to be passed to an ENGINE before attempting to initialise it, ie. before calling ENGINE_init(). The other class of commands consist of settings or operations that tweak certain behaviour or cause certain operations to take place, and these commands may work either before or after ENGINE_init(), or in some cases both. ENGINE implementations should provide indications of this in the descriptions attached to builtin control commands and/or in external product documentation.
Issuing control commands to an ENGINE
Let's illustrate by example; a function for which the caller supplies the name of the ENGINE it wishes to use, a table of string-pairs for use before initialisation, and another table for use after initialisation. Note that the string-pairs used for control commands consist of a command "name" followed by the command "parameter" - the parameter could be NULL in some cases but the name can not. This function should initialise the ENGINE (issuing the "pre" commands beforehand and the "post" commands afterwards) and set it as the default for everything except RAND and then return a boolean success or failure.
int generic_load_engine_fn(const char *engine_id,
const char **pre_cmds, int pre_num,
const char **post_cmds, int post_num)
{
ENGINE *e = ENGINE_by_id(engine_id);
if(!e) return 0;
while(pre_num--) {
if(!ENGINE_ctrl_cmd_string(e, pre_cmds[0], pre_cmds[1], 0)) {
fprintf(stderr, "Failed command (%s - %s:%s)\n", engine_id,
pre_cmds[0], pre_cmds[1] ? pre_cmds[1] : "(NULL)");
ENGINE_free(e);
return 0;
}
pre_cmds += 2;
}
if(!ENGINE_init(e)) {
fprintf(stderr, "Failed initialisation\n");
ENGINE_free(e);
return 0;
}
/* ENGINE_init() returned a functional reference, so free the structural
* reference from ENGINE_by_id(). */
ENGINE_free(e);
while(post_num--) {
if(!ENGINE_ctrl_cmd_string(e, post_cmds[0], post_cmds[1], 0)) {
fprintf(stderr, "Failed command (%s - %s:%s)\n", engine_id,
post_cmds[0], post_cmds[1] ? post_cmds[1] : "(NULL)");
ENGINE_finish(e);
return 0;
}
post_cmds += 2;
}
ENGINE_set_default(e, ENGINE_METHOD_ALL & ~ENGINE_METHOD_RAND);
/* Success */
return 1;
}
Note that ENGINE_ctrl_cmd_string() accepts a boolean argument that can relax the semantics of the function - if set non-zero it will only return failure if the ENGINE supported the given command name but failed while executing it, if the ENGINE doesn't support the command name it will simply return success without doing anything. In this case we assume the user is only supplying commands specific to the given ENGINE so we set this to FALSE.
Discovering supported control commands
It is possible to discover at run-time the names, numerical-ids, descriptions and input parameters of the control commands supported by an ENGINE using a structural reference. Note that some control commands are defined by OpenSSL itself and it will intercept and handle these control commands on behalf of the ENGINE, ie. the ENGINE's ctrl() handler is not used for the control command. openssl/engine.h defines an index, ENGINE_CMD_BASE, that all control commands implemented by ENGINEs should be numbered from. Any command value lower than this symbol is considered a "generic" command is handled directly by the OpenSSL core routines.
It is using these "core" control commands that one can discover the the control commands implemented by a given ENGINE, specifically the commands;
#define ENGINE_HAS_CTRL_FUNCTION 10
#define ENGINE_CTRL_GET_FIRST_CMD_TYPE 11
#define ENGINE_CTRL_GET_NEXT_CMD_TYPE 12
#define ENGINE_CTRL_GET_CMD_FROM_NAME 13
#define ENGINE_CTRL_GET_NAME_LEN_FROM_CMD 14
#define ENGINE_CTRL_GET_NAME_FROM_CMD 15
#define ENGINE_CTRL_GET_DESC_LEN_FROM_CMD 16
#define ENGINE_CTRL_GET_DESC_FROM_CMD 17
#define ENGINE_CTRL_GET_CMD_FLAGS 18
Whilst these commands are automatically processed by the OpenSSL framework code, they use various properties exposed by each ENGINE to process these queries. An ENGINE has 3 properties it exposes that can affect how this behaves; it can supply a ctrl() handler, it can specify ENGINE_FLAGS_MANUAL_CMD_CTRL in the ENGINE's flags, and it can expose an array of control command descriptions. If an ENGINE specifies the ENGINE_FLAGS_MANUAL_CMD_CTRL flag, then it will simply pass all these "core" control commands directly to the ENGINE's ctrl() handler (and thus, it must have supplied one), so it is up to the ENGINE to reply to these "discovery" commands itself. If that flag is not set, then the OpenSSL framework code will work with the following rules;
if no ctrl() handler supplied;
ENGINE_HAS_CTRL_FUNCTION returns FALSE (zero),
all other commands fail.
if a ctrl() handler was supplied but no array of control commands;
ENGINE_HAS_CTRL_FUNCTION returns TRUE,
all other commands fail.
if a ctrl() handler and array of control commands was supplied;
ENGINE_HAS_CTRL_FUNCTION returns TRUE,
all other commands proceed processing ...
If the ENGINE's array of control commands is empty then all other commands will fail, otherwise; ENGINE_CTRL_GET_FIRST_CMD_TYPE returns the identifier of the first command supported by the ENGINE, ENGINE_GET_NEXT_CMD_TYPE takes the identifier of a command supported by the ENGINE and returns the next command identifier or fails if there are no more, ENGINE_CMD_FROM_NAME takes a string name for a command and returns the corresponding identifier or fails if no such command name exists, and the remaining commands take a command identifier and return properties of the corresponding commands. All except ENGINE_CTRL_GET_FLAGS return the string length of a command name or description, or populate a supplied character buffer with a copy of the command name or description. ENGINE_CTRL_GET_FLAGS returns a bitwise-OR'd mask of the following possible values;
#define ENGINE_CMD_FLAG_NUMERIC (unsigned int)0x0001
#define ENGINE_CMD_FLAG_STRING (unsigned int)0x0002
#define ENGINE_CMD_FLAG_NO_INPUT (unsigned int)0x0004
#define ENGINE_CMD_FLAG_INTERNAL (unsigned int)0x0008
If the ENGINE_CMD_FLAG_INTERNAL flag is set, then any other flags are purely informational to the caller - this flag will prevent the command being usable for any higher-level ENGINE functions such as ENGINE_ctrl_cmd_string(). "INTERNAL" commands are not intended to be exposed to text-based configuration by applications, administrations, users, etc. These can support arbitrary operations via ENGINE_ctrl(), including passing to and/or from the control commands data of any arbitrary type. These commands are supported in the discovery mechanisms simply to allow applications determinie if an ENGINE supports certain specific commands it might want to use (eg. application "foo" might query various ENGINEs to see if they implement "FOO_GET_VENDOR_LOGO_GIF" - and ENGINE could therefore decide whether or not to support this "foo"-specific extension).