Skip to content

Commit

Permalink
OpenSSL::PKey.from_parameters for generating PKey objects from parame…
Browse files Browse the repository at this point in the history
…ter values
  • Loading branch information
anakinj committed Dec 9, 2023
1 parent 1fa9fc5 commit a5dc485
Show file tree
Hide file tree
Showing 2 changed files with 410 additions and 0 deletions.
197 changes: 197 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,174 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
return ossl_pkey_new(gen_arg.pkey);
}

#if OSSL_OPENSSL_PREREQ(3, 0, 0)
#include <openssl/param_build.h>
#include <openssl/core_names.h>

struct pkey_from_parameters_alias {
char alias[10];
char param_name[20];
};

static const struct pkey_from_parameters_alias rsa_aliases[] = {
{ "p", OSSL_PKEY_PARAM_RSA_FACTOR1 },
{ "q", OSSL_PKEY_PARAM_RSA_FACTOR2 },
{ "dmp1", OSSL_PKEY_PARAM_RSA_EXPONENT1 },
{ "dmq1", OSSL_PKEY_PARAM_RSA_EXPONENT2 },
{ "iqmp", OSSL_PKEY_PARAM_RSA_COEFFICIENT1 },
{ "", "" }
};

static const struct pkey_from_parameters_alias fcc_aliases[] = {
{ "pub_key", OSSL_PKEY_PARAM_PUB_KEY },
{ "priv_key", OSSL_PKEY_PARAM_PRIV_KEY },
{ "", "" }
};

struct pkey_from_parameters_arg {
OSSL_PARAM_BLD *param_bld;
const OSSL_PARAM *settable_params;
const struct pkey_from_parameters_alias *aliases;
};

static int
add_parameter_to_builder(VALUE key, VALUE value, VALUE arg) {
if(NIL_P(value))
return ST_CONTINUE;

if (SYMBOL_P(key))
key = rb_sym2str(key);

const char *key_ptr = StringValueCStr(key);
const struct pkey_from_parameters_arg *params = (const struct pkey_from_parameters_arg *) arg;

for(int i = 0; strlen(params->aliases[i].alias) > 0; i++) {
if(strcmp(params->aliases[i].alias, key_ptr) == 0) {
key_ptr = params->aliases[i].param_name;
break;
}
}

for (const OSSL_PARAM *settable_params = params->settable_params; settable_params->key != NULL; settable_params++) {
if(strcmp(settable_params->key, key_ptr) == 0) {
switch (settable_params->data_type) {
case OSSL_PARAM_INTEGER:
case OSSL_PARAM_UNSIGNED_INTEGER:
if(!OSSL_PARAM_BLD_push_BN(params->param_bld, key_ptr, GetBNPtr(value))) {
OSSL_PARAM_BLD_free(params->param_bld);
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_BN");
}
break;
case OSSL_PARAM_UTF8_STRING:
StringValue(value);
if(!OSSL_PARAM_BLD_push_utf8_string(params->param_bld, key_ptr, RSTRING_PTR(value), RSTRING_LENINT(value))) {
OSSL_PARAM_BLD_free(params->param_bld);
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_utf8_string");
}
break;

case OSSL_PARAM_OCTET_STRING:
StringValue(value);
if(!OSSL_PARAM_BLD_push_octet_string(params->param_bld, key_ptr, RSTRING_PTR(value), RSTRING_LENINT(value))) {
OSSL_PARAM_BLD_free(params->param_bld);
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_octet_string");
}
break;
case OSSL_PARAM_UTF8_PTR:
case OSSL_PARAM_OCTET_PTR:
OSSL_PARAM_BLD_free(params->param_bld);
ossl_raise(ePKeyError, "Unsupported parameter \"%s\", handling of OSSL_PARAM_UTF8_PTR and OSSL_PARAM_OCTET_PTR not implemented", key_ptr);
break;
}

return ST_CONTINUE;
}
}
OSSL_PARAM_BLD_free(params->param_bld);

char message_buffer[512] = { 0 };
char *cur = message_buffer;
char *end = message_buffer + sizeof(message_buffer);
for (const OSSL_PARAM *settable_params = params->settable_params; settable_params->key != NULL; settable_params++) {
const char *fmt = cur == message_buffer ? "%s" : ", %s";
if (cur > end)
break;
cur += snprintf(cur, end-cur, fmt, settable_params->key);
}

for(int i = 0; strlen(params->aliases[i].alias) > 0; i++) {
const char *fmt = cur == message_buffer ? "%s" : ", %s";
if (cur > end)
break;
cur += snprintf(cur, end-cur, fmt, params->aliases[i].alias);
}

ossl_raise(ePKeyError, "Invalid parameter \"%s\". Supported parameters: \"%s\"", key_ptr, message_buffer);
}

static VALUE
pkey_from_parameters(int argc, VALUE *argv, VALUE self)
{
VALUE alg, options;
rb_scan_args(argc, argv, "11", &alg, &options);

const char* algorithm = StringValueCStr(alg);

EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, algorithm, NULL);

if (ctx == NULL)
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_from_name");

struct pkey_from_parameters_arg from_params_args = { 0 };

from_params_args.param_bld = OSSL_PARAM_BLD_new();

if (from_params_args.param_bld == NULL) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_new");
}

from_params_args.settable_params = EVP_PKEY_fromdata_settable(ctx, EVP_PKEY_KEYPAIR);

if (from_params_args.settable_params == NULL) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_fromdata_settable");
}

if (strcmp("RSA", algorithm) == 0)
from_params_args.aliases = rsa_aliases;
else
from_params_args.aliases = fcc_aliases;

rb_hash_foreach(options, &add_parameter_to_builder, (VALUE) &from_params_args);

OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(from_params_args.param_bld);
OSSL_PARAM_BLD_free(from_params_args.param_bld);

if (params == NULL) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_to_param");
}

EVP_PKEY *pkey = NULL;

if (EVP_PKEY_fromdata_init(ctx) <= 0) {
EVP_PKEY_CTX_free(ctx);
ossl_raise(ePKeyError, "EVP_PKEY_fromdata_init");
}

if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
ossl_raise(ePKeyError, "EVP_PKEY_fromdata");
}

EVP_PKEY_CTX_free(ctx);

return ossl_pkey_new(pkey);
}
#endif

/*
* call-seq:
* OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
Expand Down Expand Up @@ -498,6 +666,33 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
return pkey_generate(argc, argv, self, 0);
}

/*
* call-seq:
* OpenSSL::PKey.from_parameters(algo_name, parameters) -> pkey
*
* Generates a new key based on given key parameters.
* NOTE: Will only work on OpenSSL 3.0 or later.
*
* The first parameter is the type of the key to create, given as a String, for example RSA, DSA, EC etc.
* Second parameter is the parameters to be used for the key.
*
* For details algorithms and parameters see https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html
*
* == Example
* pkey = OpenSSL::PKey.from_parameters("RSA", n: 3161751493, e: 65537, d: 2064855961)
* pkey.private? #=> true
* pkey.public_key #=> #<OpenSSL::PKey::RSA...
*/
static VALUE
ossl_pkey_s_from_parameters(int argc, VALUE *argv, VALUE self)
{
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
return pkey_from_parameters(argc, argv, self);
#else
rb_raise(ePKeyError, "Only supported with OpenSSL 3.0");
#endif
}

/*
* TODO: There is no convenient way to check the presence of public key
* components on OpenSSL 3.0. But since keys are immutable on 3.0, pkeys without
Expand Down Expand Up @@ -1751,6 +1946,8 @@ Init_ossl_pkey(void)
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
rb_define_module_function(mPKey, "from_parameters", ossl_pkey_s_from_parameters, -1);

#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
rb_define_module_function(mPKey, "new_raw_private_key", ossl_pkey_new_raw_private_key, 2);
rb_define_module_function(mPKey, "new_raw_public_key", ossl_pkey_new_raw_public_key, 2);
Expand Down
Loading

0 comments on commit a5dc485

Please sign in to comment.