为什么 openssl 引擎的引用计数如此之高?

Why ref count for an openssl engine is so high?

在openssl引擎API中有一个名为ENGINE_finish(e)的清理命令,它调用在引擎e中实现并注册的"finish"命令。

仅当引擎的引用计数等于 1 时才会调用引擎的 "finish" 命令。我不明白为什么在我的情况下它是 3。当您使用 libcurl 时,引用计数将调用方法后也会增加 1 POST,它将是 4。

为了确保 "finish" 命令被调用,我需要 运行 一个丑陋的循环,如下所示,我不知道它是否会对其他组件造成潜在的损害:

    while (e->funct_ref) {
    ENGINE_finish(e);
}

我已经删除了我的初始代码来演示错误并使其变得非常简单。 "dummy"引擎的代码和客户端的代码如下。请让我知道如何使 ENGINE_finish 在没有上面丑陋的代码的情况下工作,以及为什么引用计数等于 3 而它应该是 1。

虚拟引擎:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/ecdsa.h>
#include <openssl/engine.h>
#include <openssl/eng_int.h>

static int dummy_destroy(ENGINE *e);
static int dummy_init(ENGINE *e);
static int dummy_finish(ENGINE *e);
static int dummy_ctrl(ENGINE *e, int cmd, long i, void *p,
                             void (*f) ());






static int dummy_rand_bytes(unsigned char *buf, int num);
static void dummy_rand_seed(const void  *buf, int num);
static void dummy_rand_seed(const void *buf, int num) {
}
static void dummy_rand_cleanup();
static void dummy_rand_cleanup(){
}
static void dummy_rand_add(const void *buf, int num, double entropy);
static void dummy_rand_add(const void *buf, int num, double entropy){
}
static int dummy_rand_status(){
    fprintf(stderr, "dummy_rand_status\n");
    return 1;
}


#  define DUMMY_CMD_SO_PATH                ENGINE_CMD_BASE

static const ENGINE_CMD_DEFN dummy_cmd_defns[] = {
        {DUMMY_CMD_SO_PATH,
         "SO_PATH",
         "Specifies the path to the  dummy shared library",
         ENGINE_CMD_FLAG_STRING},
    {0, NULL, NULL, 0}
};

static char *my_prog = "dummy";
static EC_KEY *ec_key=NULL;
static char *key_file = "path-to-ec-key";


int dummy_ecdsa_sign_setup (EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp,
        BIGNUM **rp);

static ECDSA_SIG *dummy_ecdsa_sign (const unsigned char *dgst, int dgst_len,
        const BIGNUM *kinv, const BIGNUM *rp,
        EC_KEY *in_eckey);

int dummy_ecdsa_do_verify (const unsigned char *digest, int digest_len,
        const ECDSA_SIG *ecdsa_sig, EC_KEY *eckey);


static ECDSA_METHOD dummy_ecdsa = {
        "dummy ECDSA method",
        dummy_ecdsa_sign,
        dummy_ecdsa_sign_setup,
        dummy_ecdsa_do_verify,
        0,                          /* flags */
        NULL                        /* app_data */
};

static RAND_METHOD dummy_rand = {
    /* "Cluster Labs RAND method", */
    dummy_rand_seed,                       /* seed */
    dummy_rand_bytes,    /* bytes */
    dummy_rand_cleanup,                       /* cleanup */
    dummy_rand_add,                       /* add */
    dummy_rand_bytes,    /* pseudorand */
    dummy_rand_status,                       /* status */
};

static const char *engine_dummy_id = "dummy";
static const char *engine_dummy_name =
    "DUMMY Secure Element Support";

/* engine implementation */
/* ---------------------*/
static int bind_helper(ENGINE *e)
{

    if (!ENGINE_set_id(e, engine_dummy_id) ||
        !ENGINE_set_name(e, engine_dummy_name) ||
        !ENGINE_set_RAND(e, &dummy_rand) ||
        !ENGINE_set_ECDSA(e, &dummy_ecdsa) ||
        !ENGINE_set_destroy_function(e, dummy_destroy) ||
        !ENGINE_set_init_function(e, dummy_init) ||
        !ENGINE_set_finish_function(e, dummy_finish) ||
        !ENGINE_set_ctrl_function(e, dummy_ctrl) ||
        !ENGINE_set_cmd_defns(e, dummy_cmd_defns))
        return 0;
    return 1;
}


static int dummy_destroy(ENGINE *e)
{

    fprintf(stderr, "%s: DESTROYED\n", my_prog);
    return 1;
}

int dummy_init(ENGINE *e)
{
    fprintf(stderr, "%s: INIT ref cnt: %d\n", my_prog, e->funct_ref);

    FILE *fp = fopen(key_file, "r");
    if (!fp) {
        fprintf(stderr,"%s: Can't open %s\n", my_prog, key_file);
        return 0;
    }

    EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, 0, NULL);
    if (pkey) {
        ec_key = EVP_PKEY_get1_EC_KEY(pkey);
        fprintf(stderr,"%s: Got ec key %p\n", my_prog, ec_key);
        EVP_PKEY_free (pkey);
    }

    fprintf(stderr, "%s: INIT ENDS ref cnt: %d\n", my_prog, e->funct_ref);

    return 1;
}

static int dummy_finish(ENGINE *e)
{

   fprintf(stderr, "%s: FINISHED\n", my_prog);
   return (1);

}

static int dummy_ctrl(ENGINE *e, int cmd, long i, void *p,
                             void (*f) ())
{
    fprintf(stderr, "dummy_trl cmd %d\n", cmd);
    switch (cmd) {
    case DUMMY_CMD_SO_PATH:
        if (p == NULL) {
           return 0;
        }
        return 1;
    default:
        break;
    }
    return 0;
}


static int dummy_rand_bytes(unsigned char *buf, int num)
{
    fprintf(stderr, "dummy_rand num = %d \n", num);
    fflush(stderr);
    for (int i=0; i < num; i++) {
        buf[i]=rand();
    }
    return 1;  

}
int dummy_ecdsa_sign_setup (EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp,
        BIGNUM **rp)
{
    return 1;
}
static ECDSA_SIG *dummy_ecdsa_sign (const unsigned char *dgst, int dgst_len,
        const BIGNUM *kinv, const BIGNUM *rp,
        EC_KEY *in_eckey) {
    printf("dummy engine ecdsa sign digest \n");
    if (ec_key != NULL) {
        fprintf(stderr, "%s: got private ec_key\n", my_prog);
        in_eckey =  ec_key;
    }
    return ECDSA_do_sign_ex(dgst, dgst_len, kinv, rp, in_eckey);
}

int dummy_ecdsa_do_verify (const unsigned char *dgst, int dgst_len,
        const ECDSA_SIG *ecdsa_sig, EC_KEY *eckey) {
    printf("dummy engine verifying function\n");
    return ECDSA_do_verify(dgst, dgst_len, ecdsa_sig, eckey);
}

#  ifdef ENGINE_DYNAMIC_SUPPORT
static int bind_fn(ENGINE *e, const char *id)
{
    fprintf(stderr, "bind_fn DUMMY\n");
    if (id && (strcmp(id, engine_dummy_id) != 0)) {
        fprintf(stderr, "bind_fn return(0) first\n");
        return 0;
    }
    if (!bind_helper(e)) {
        fprintf(stderr, "bind_fn return(1) first\n");
        return 0;
    }
    fprintf(stderr, "bind_fn return(1) %d\n", e->funct_ref);
    return 1;
}

IMPLEMENT_DYNAMIC_CHECK_FN()
    IMPLEMENT_DYNAMIC_BIND_FN(bind_fn)
#  endif                        /* ENGINE_DYNAMIC_SUPPORT */

客户代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <openssl/engine.h>
#include <openssl/eng_int.h>

static char *myprog = "engtest";

ENGINE *set_engine (const char * name) {
    ENGINE *e = NULL;
    ENGINE_load_builtin_engines();
    e = ENGINE_by_id(name);
    if(!e || !ENGINE_init(e)) {
#ifdef DEBUG
        fprintf(stderr,"%s: can't find or init engine %s %p\n", myprog, name, e);
#endif
        if (e)
            ENGINE_free(e);

        return NULL;
    }
    if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)){
#ifdef DEBUG
        fprintf(stderr,"%s: can't set engine %s as default %p\n", myprog, name, e);
#endif
        ENGINE_finish(e);
        ENGINE_free(e);
        return NULL;
    }
    fprintf(stderr, "%s: set eng %s %p %p, ref cnt = %d \n", myprog, name, e, e->finish, e->funct_ref);
    return e;
}

int unset_engine (ENGINE * e) {
    if (!e)
        return 0;

    ENGINE_set_RAND(e, NULL);
    ENGINE_set_ECDSA(e, NULL);
    ENGINE_set_default(NULL, 0);

    fprintf(stderr, "%s: unset %p, ref cnt = %d\n", myprog, e, e->funct_ref);

    while (e->funct_ref) {
        ENGINE_finish(e);
    }

    ENGINE_free(e);

    return 1;
}

int main (int argc, char *argv[]) {
    ENGINE *e = set_engine("dummy");
    unset_engine(e);
    return 0;
}

输出如下所示。它在 init 时为 0,然后以某种方式被撞到 3。

bind_fn DUMMY
bind_fn return(1) 0
dummy: INIT ref cnt: 0
dummy: Got ec key 0x7f8199c04b70
dummy: INIT ENDS ref cnt: 0
engtest: set eng dummy 0x7f8199c04180 0x101bd69a0, ref cnt = 3 
engtest: unset 0x7f8199c04180, ref cnt =3
dummy: FINISHED

在 OpenSSL 中清理引擎(和某些其他类型的数据)的整个机制对我来说似乎相当脆弱,而且您的问题看起来很熟悉。除了未释放的内存之外,如果某些清理调用未按正确顺序执行,则很容易 运行 崩溃。我没有努力重现你的特定问题,但这里有一段代码,我用它来干净地释放我的引擎,所以你可以试一试:

OBJ_cleanup();
EVP_cleanup();
if (NULL != S_engine) {
    ENGINE_unregister_ciphers(S_engine);
    ENGINE_unregister_digests(S_engine);
    ENGINE_unregister_ECDH(S_engine);
    ENGINE_unregister_ECDSA(S_engine);
    ENGINE_unregister_RAND(S_engine);
    ENGINE_finish(S_engine);
    ENGINE_remove(S_engine);
    S_engine = NULL;
}
ENGINE_cleanup();