ruby/ext/openssl/ossl_engine.c

577 строки
14 KiB
C

/*
* 'OpenSSL for Ruby' project
* Copyright (C) 2003 GOTOU Yuuzou <gotoyuzo@notwork.org>
* All rights reserved.
*/
/*
* This program is licensed under the same licence as Ruby.
* (See the file 'LICENCE'.)
*/
#include "ossl.h"
#if !defined(OPENSSL_NO_ENGINE)
#define NewEngine(klass) \
TypedData_Wrap_Struct((klass), &ossl_engine_type, 0)
#define SetEngine(obj, engine) do { \
if (!(engine)) { \
ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \
} \
RTYPEDDATA_DATA(obj) = (engine); \
} while(0)
#define GetEngine(obj, engine) do { \
TypedData_Get_Struct((obj), ENGINE, &ossl_engine_type, (engine)); \
if (!(engine)) { \
ossl_raise(rb_eRuntimeError, "ENGINE wasn't initialized."); \
} \
} while (0)
#define SafeGetEngine(obj, engine) do { \
OSSL_Check_Kind((obj), cEngine); \
GetPKCS7((obj), (engine)); \
} while (0)
/*
* Classes
*/
/* Document-class: OpenSSL::Engine
*
* This class is the access to openssl's ENGINE cryptographic module
* implementation.
*
* See also, https://www.openssl.org/docs/crypto/engine.html
*/
VALUE cEngine;
/* Document-class: OpenSSL::Engine::EngineError
*
* This is the generic exception for OpenSSL::Engine related errors
*/
VALUE eEngineError;
/*
* Private
*/
#define OSSL_ENGINE_LOAD_IF_MATCH(x) \
do{\
if(!strcmp(#x, RSTRING_PTR(name))){\
ENGINE_load_##x();\
return Qtrue;\
}\
}while(0)
static void
ossl_engine_free(void *engine)
{
ENGINE_free(engine);
}
static const rb_data_type_t ossl_engine_type = {
"OpenSSL/Engine",
{
0, ossl_engine_free,
},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
};
/* Document-method: OpenSSL::Engine.load
*
* call-seq:
* load(enginename = nil)
*
* This method loads engines. If +name+ is nil, then all builtin engines are
* loaded. Otherwise, the given +name+, as a string, is loaded if available to
* your runtime, and returns true. If +name+ is not found, then nil is
* returned.
*
*/
static VALUE
ossl_engine_s_load(int argc, VALUE *argv, VALUE klass)
{
#if !defined(HAVE_ENGINE_LOAD_BUILTIN_ENGINES)
return Qnil;
#else
VALUE name;
rb_scan_args(argc, argv, "01", &name);
if(NIL_P(name)){
ENGINE_load_builtin_engines();
return Qtrue;
}
StringValueCStr(name);
#ifndef OPENSSL_NO_STATIC_ENGINE
#if HAVE_ENGINE_LOAD_DYNAMIC
OSSL_ENGINE_LOAD_IF_MATCH(dynamic);
#endif
#if HAVE_ENGINE_LOAD_4758CCA
OSSL_ENGINE_LOAD_IF_MATCH(4758cca);
#endif
#if HAVE_ENGINE_LOAD_AEP
OSSL_ENGINE_LOAD_IF_MATCH(aep);
#endif
#if HAVE_ENGINE_LOAD_ATALLA
OSSL_ENGINE_LOAD_IF_MATCH(atalla);
#endif
#if HAVE_ENGINE_LOAD_CHIL
OSSL_ENGINE_LOAD_IF_MATCH(chil);
#endif
#if HAVE_ENGINE_LOAD_CSWIFT
OSSL_ENGINE_LOAD_IF_MATCH(cswift);
#endif
#if HAVE_ENGINE_LOAD_NURON
OSSL_ENGINE_LOAD_IF_MATCH(nuron);
#endif
#if HAVE_ENGINE_LOAD_SUREWARE
OSSL_ENGINE_LOAD_IF_MATCH(sureware);
#endif
#if HAVE_ENGINE_LOAD_UBSEC
OSSL_ENGINE_LOAD_IF_MATCH(ubsec);
#endif
#if HAVE_ENGINE_LOAD_PADLOCK
OSSL_ENGINE_LOAD_IF_MATCH(padlock);
#endif
#if HAVE_ENGINE_LOAD_CAPI
OSSL_ENGINE_LOAD_IF_MATCH(capi);
#endif
#if HAVE_ENGINE_LOAD_GMP
OSSL_ENGINE_LOAD_IF_MATCH(gmp);
#endif
#if HAVE_ENGINE_LOAD_GOST
OSSL_ENGINE_LOAD_IF_MATCH(gost);
#endif
#if HAVE_ENGINE_LOAD_CRYPTODEV
OSSL_ENGINE_LOAD_IF_MATCH(cryptodev);
#endif
#if HAVE_ENGINE_LOAD_AESNI
OSSL_ENGINE_LOAD_IF_MATCH(aesni);
#endif
#endif
#ifdef HAVE_ENGINE_LOAD_OPENBSD_DEV_CRYPTO
OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto);
#endif
OSSL_ENGINE_LOAD_IF_MATCH(openssl);
rb_warning("no such builtin loader for `%"PRIsVALUE"'", name);
return Qnil;
#endif /* HAVE_ENGINE_LOAD_BUILTIN_ENGINES */
}
/* Document-method: OpenSSL::Engine.cleanup
* call-seq:
* OpenSSL::Engine.cleanup
*
* It is only necessary to run cleanup when engines are loaded via
* OpenSSL::Engine.load. However, running cleanup before exit is recommended.
*
* Note that this is needed and works only in OpenSSL < 1.1.0.
*
* See also, https://www.openssl.org/docs/crypto/engine.html
*/
static VALUE
ossl_engine_s_cleanup(VALUE self)
{
ENGINE_cleanup();
return Qnil;
}
/* Document-method: OpenSSL::Engine.engines
*
* Returns an array of currently loaded engines.
*/
static VALUE
ossl_engine_s_engines(VALUE klass)
{
ENGINE *e;
VALUE ary, obj;
ary = rb_ary_new();
for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)){
obj = NewEngine(klass);
/* Need a ref count of two here because of ENGINE_free being
* called internally by OpenSSL when moving to the next ENGINE
* and by us when releasing the ENGINE reference */
ENGINE_up_ref(e);
SetEngine(obj, e);
rb_ary_push(ary, obj);
}
return ary;
}
/* Document-method: OpenSSL::Engine.by_id
*
* call-seq:
* by_id(name) -> engine
*
* Fetch the engine as specified by the +id+ String
*
* OpenSSL::Engine.by_id("openssl")
* => #<OpenSSL::Engine id="openssl" name="Software engine support">
*
* See OpenSSL::Engine.engines for the currently loaded engines
*/
static VALUE
ossl_engine_s_by_id(VALUE klass, VALUE id)
{
ENGINE *e;
VALUE obj;
StringValueCStr(id);
ossl_engine_s_load(1, &id, klass);
obj = NewEngine(klass);
if(!(e = ENGINE_by_id(RSTRING_PTR(id))))
ossl_raise(eEngineError, NULL);
SetEngine(obj, e);
if(rb_block_given_p()) rb_yield(obj);
if(!ENGINE_init(e))
ossl_raise(eEngineError, NULL);
ENGINE_ctrl(e, ENGINE_CTRL_SET_PASSWORD_CALLBACK,
0, NULL, (void(*)(void))ossl_pem_passwd_cb);
ossl_clear_error();
return obj;
}
static VALUE
ossl_engine_s_alloc(VALUE klass)
{
ENGINE *e;
VALUE obj;
obj = NewEngine(klass);
if (!(e = ENGINE_new())) {
ossl_raise(eEngineError, NULL);
}
SetEngine(obj, e);
return obj;
}
/* Document-method: OpenSSL::Engine#id
*
* Get the id for this engine
*
* OpenSSL::Engine.load
* OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...]
* OpenSSL::Engine.engines.first.id
* #=> "rsax"
*/
static VALUE
ossl_engine_get_id(VALUE self)
{
ENGINE *e;
GetEngine(self, e);
return rb_str_new2(ENGINE_get_id(e));
}
/* Document-method: OpenSSL::Engine#name
*
* Get the descriptive name for this engine
*
* OpenSSL::Engine.load
* OpenSSL::Engine.engines #=> [#<OpenSSL::Engine#>, ...]
* OpenSSL::Engine.engines.first.name
* #=> "RSAX engine support"
*
*/
static VALUE
ossl_engine_get_name(VALUE self)
{
ENGINE *e;
GetEngine(self, e);
return rb_str_new2(ENGINE_get_name(e));
}
/* Document-method: OpenSSL::Engine#finish
*
* Releases all internal structural references for this engine.
*
* May raise an EngineError if the engine is unavailable
*/
static VALUE
ossl_engine_finish(VALUE self)
{
ENGINE *e;
GetEngine(self, e);
if(!ENGINE_finish(e)) ossl_raise(eEngineError, NULL);
return Qnil;
}
/* Document-method: OpenSSL::Engine#cipher
*
* call-seq:
* engine.cipher(name) -> OpenSSL::Cipher
*
* This returns an OpenSSL::Cipher by +name+, if it is available in this
* engine.
*
* A EngineError will be raised if the cipher is unavailable.
*
* e = OpenSSL::Engine.by_id("openssl")
* => #<OpenSSL::Engine id="openssl" name="Software engine support">
* e.cipher("RC4")
* => #<OpenSSL::Cipher:0x007fc5cacc3048>
*
*/
static VALUE
ossl_engine_get_cipher(VALUE self, VALUE name)
{
ENGINE *e;
const EVP_CIPHER *ciph, *tmp;
int nid;
tmp = EVP_get_cipherbyname(StringValueCStr(name));
if(!tmp) ossl_raise(eEngineError, "no such cipher `%"PRIsVALUE"'", name);
nid = EVP_CIPHER_nid(tmp);
GetEngine(self, e);
ciph = ENGINE_get_cipher(e, nid);
if(!ciph) ossl_raise(eEngineError, NULL);
return ossl_cipher_new(ciph);
}
/* Document-method: OpenSSL::Engine#digest
*
* call-seq:
* engine.digest(name) -> OpenSSL::Digest
*
* This returns an OpenSSL::Digest by +name+.
*
* Will raise an EngineError if the digest is unavailable.
*
* e = OpenSSL::Engine.by_id("openssl")
* #=> #<OpenSSL::Engine id="openssl" name="Software engine support">
* e.digest("SHA1")
* #=> #<OpenSSL::Digest: da39a3ee5e6b4b0d3255bfef95601890afd80709>
* e.digest("zomg")
* #=> OpenSSL::Engine::EngineError: no such digest `zomg'
*/
static VALUE
ossl_engine_get_digest(VALUE self, VALUE name)
{
ENGINE *e;
const EVP_MD *md, *tmp;
int nid;
tmp = EVP_get_digestbyname(StringValueCStr(name));
if(!tmp) ossl_raise(eEngineError, "no such digest `%"PRIsVALUE"'", name);
nid = EVP_MD_nid(tmp);
GetEngine(self, e);
md = ENGINE_get_digest(e, nid);
if(!md) ossl_raise(eEngineError, NULL);
return ossl_digest_new(md);
}
/* Document-method: OpenSSL::Engine#load_private_key
*
* call-seq:
* engine.load_private_key(id = nil, data = nil) -> OpenSSL::PKey
*
* Loads the given private key by +id+ and +data+.
*
* An EngineError is raised of the OpenSSL::PKey is unavailable.
*
*/
static VALUE
ossl_engine_load_privkey(int argc, VALUE *argv, VALUE self)
{
ENGINE *e;
EVP_PKEY *pkey;
VALUE id, data, obj;
char *sid, *sdata;
rb_scan_args(argc, argv, "02", &id, &data);
sid = NIL_P(id) ? NULL : StringValueCStr(id);
sdata = NIL_P(data) ? NULL : StringValueCStr(data);
GetEngine(self, e);
pkey = ENGINE_load_private_key(e, sid, NULL, sdata);
if (!pkey) ossl_raise(eEngineError, NULL);
obj = ossl_pkey_new(pkey);
OSSL_PKEY_SET_PRIVATE(obj);
return obj;
}
/* Document-method: OpenSSL::Engine#load_public_key
*
* call-seq:
* engine.load_public_key(id = nil, data = nil) -> OpenSSL::PKey
*
* Loads the given private key by +id+ and +data+.
*
* An EngineError is raised of the OpenSSL::PKey is unavailable.
*
*/
static VALUE
ossl_engine_load_pubkey(int argc, VALUE *argv, VALUE self)
{
ENGINE *e;
EVP_PKEY *pkey;
VALUE id, data;
char *sid, *sdata;
rb_scan_args(argc, argv, "02", &id, &data);
sid = NIL_P(id) ? NULL : StringValueCStr(id);
sdata = NIL_P(data) ? NULL : StringValueCStr(data);
GetEngine(self, e);
pkey = ENGINE_load_public_key(e, sid, NULL, sdata);
if (!pkey) ossl_raise(eEngineError, NULL);
return ossl_pkey_new(pkey);
}
/* Document-method: OpenSSL::Engine#set_default
*
* call-seq:
* engine.set_default(flag)
*
* Set the defaults for this engine with the given +flag+.
*
* These flags are used to control combinations of algorithm methods.
*
* +flag+ can be one of the following, other flags are available depending on
* your OS.
*
* [All flags] 0xFFFF
* [No flags] 0x0000
*
* See also <openssl/engine.h>
*/
static VALUE
ossl_engine_set_default(VALUE self, VALUE flag)
{
ENGINE *e;
int f = NUM2INT(flag);
GetEngine(self, e);
ENGINE_set_default(e, f);
return Qtrue;
}
/* Document-method: OpenSSL::Engine#ctrl_cmd
*
* call-seq:
* engine.ctrl_cmd(command, value = nil) -> engine
*
* Send the given +command+ to this engine.
*
* Raises an EngineError if the +command+ fails.
*/
static VALUE
ossl_engine_ctrl_cmd(int argc, VALUE *argv, VALUE self)
{
ENGINE *e;
VALUE cmd, val;
int ret;
GetEngine(self, e);
rb_scan_args(argc, argv, "11", &cmd, &val);
ret = ENGINE_ctrl_cmd_string(e, StringValueCStr(cmd),
NIL_P(val) ? NULL : StringValueCStr(val), 0);
if (!ret) ossl_raise(eEngineError, NULL);
return self;
}
static VALUE
ossl_engine_cmd_flag_to_name(int flag)
{
switch(flag){
case ENGINE_CMD_FLAG_NUMERIC: return rb_str_new2("NUMERIC");
case ENGINE_CMD_FLAG_STRING: return rb_str_new2("STRING");
case ENGINE_CMD_FLAG_NO_INPUT: return rb_str_new2("NO_INPUT");
case ENGINE_CMD_FLAG_INTERNAL: return rb_str_new2("INTERNAL");
default: return rb_str_new2("UNKNOWN");
}
}
/* Document-method: OpenSSL::Engine#cmds
*
* Returns an array of command definitions for the current engine
*/
static VALUE
ossl_engine_get_cmds(VALUE self)
{
ENGINE *e;
const ENGINE_CMD_DEFN *defn, *p;
VALUE ary, tmp;
GetEngine(self, e);
ary = rb_ary_new();
if ((defn = ENGINE_get_cmd_defns(e)) != NULL){
for (p = defn; p->cmd_num > 0; p++){
tmp = rb_ary_new();
rb_ary_push(tmp, rb_str_new2(p->cmd_name));
rb_ary_push(tmp, rb_str_new2(p->cmd_desc));
rb_ary_push(tmp, ossl_engine_cmd_flag_to_name(p->cmd_flags));
rb_ary_push(ary, tmp);
}
}
return ary;
}
/* Document-method: OpenSSL::Engine#inspect
*
* Pretty print this engine
*/
static VALUE
ossl_engine_inspect(VALUE self)
{
ENGINE *e;
GetEngine(self, e);
return rb_sprintf("#<%"PRIsVALUE" id=\"%s\" name=\"%s\">",
rb_obj_class(self), ENGINE_get_id(e), ENGINE_get_name(e));
}
#define DefEngineConst(x) rb_define_const(cEngine, #x, INT2NUM(ENGINE_##x))
void
Init_ossl_engine(void)
{
cEngine = rb_define_class_under(mOSSL, "Engine", rb_cObject);
eEngineError = rb_define_class_under(cEngine, "EngineError", eOSSLError);
rb_define_alloc_func(cEngine, ossl_engine_s_alloc);
rb_define_singleton_method(cEngine, "load", ossl_engine_s_load, -1);
rb_define_singleton_method(cEngine, "cleanup", ossl_engine_s_cleanup, 0);
rb_define_singleton_method(cEngine, "engines", ossl_engine_s_engines, 0);
rb_define_singleton_method(cEngine, "by_id", ossl_engine_s_by_id, 1);
rb_undef_method(CLASS_OF(cEngine), "new");
rb_define_method(cEngine, "id", ossl_engine_get_id, 0);
rb_define_method(cEngine, "name", ossl_engine_get_name, 0);
rb_define_method(cEngine, "finish", ossl_engine_finish, 0);
rb_define_method(cEngine, "cipher", ossl_engine_get_cipher, 1);
rb_define_method(cEngine, "digest", ossl_engine_get_digest, 1);
rb_define_method(cEngine, "load_private_key", ossl_engine_load_privkey, -1);
rb_define_method(cEngine, "load_public_key", ossl_engine_load_pubkey, -1);
rb_define_method(cEngine, "set_default", ossl_engine_set_default, 1);
rb_define_method(cEngine, "ctrl_cmd", ossl_engine_ctrl_cmd, -1);
rb_define_method(cEngine, "cmds", ossl_engine_get_cmds, 0);
rb_define_method(cEngine, "inspect", ossl_engine_inspect, 0);
DefEngineConst(METHOD_RSA);
DefEngineConst(METHOD_DSA);
DefEngineConst(METHOD_DH);
DefEngineConst(METHOD_RAND);
#ifdef ENGINE_METHOD_BN_MOD_EXP
DefEngineConst(METHOD_BN_MOD_EXP);
#endif
#ifdef ENGINE_METHOD_BN_MOD_EXP_CRT
DefEngineConst(METHOD_BN_MOD_EXP_CRT);
#endif
DefEngineConst(METHOD_CIPHERS);
DefEngineConst(METHOD_DIGESTS);
DefEngineConst(METHOD_ALL);
DefEngineConst(METHOD_NONE);
}
#else
void
Init_ossl_engine(void)
{
}
#endif