OpenSSL 上的错误 "incomplete class type 'evp_pkey_st' is not allowed" 和“'CRYPTO_mem_leaks' 未定义”

Errors "incomplete class type 'evp_pkey_st' is not allowed" and "'CRYPTO_mem_leaks' is undefined" on OpenSSL

我目前正在尝试使用 OpenSSL(版本 1.1.1)库以编程方式创建 public/private 密钥对,但我无法编译我的程序,因为我收到以下错误:

pointer to incomplete class type "evp_pkey_st" is not allowed

identifier "CRYPTO_mem_leaks" is undefined

我知道 OpenSSL 已安装并且正在被识别,因为我已经毫无问题地包含了各种 OpenSSL 头文件。唯一给我一个问题的是 openssl/evp.h,VSCode 告诉我找不到。有谁知道如何解决这些问题?谢谢。

/* Certificate creation. Demonstrates some certificate related
 * operations.
 */


#include <stdio.h>
#include <stdlib.h>

#include <openssl/pem.h>
#include <openssl/conf.h>
#include <openssl/x509v3.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif

int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days);
int add_ext(X509 *cert, int nid, char *value);

int main(int argc, char **argv) {
    BIO *bio_err;
    X509 *x509=NULL;
    EVP_PKEY *pkey=NULL;

    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);

    bio_err=BIO_new_fp(stderr, BIO_NOCLOSE);

    mkcert(&x509,&pkey,512,0,365);

    RSA_print_fp(stdout,pkey->pkey.rsa,0);
    X509_print_fp(stdout,x509);

    PEM_write_PrivateKey(stdout,pkey,NULL,NULL,0,NULL, NULL);
    PEM_write_X509(stdout,x509);

    X509_free(x509);
    EVP_PKEY_free(pkey);

#ifndef OPENSSL_NO_ENGINE
    ENGINE_cleanup();
#endif
    CRYPTO_cleanup_all_ex_data();

    CRYPTO_mem_leaks(bio_err);
    BIO_free(bio_err);
    return(0);
    }

static void callback(int p, int n, void *arg)
    {
    char c='B';

    if (p == 0) c='.';
    if (p == 1) c='+';
    if (p == 2) c='*';
    if (p == 3) c='\n';
    fputc(c,stderr);
    }

int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days)
    {
    X509 *x;
    EVP_PKEY *pk;
    RSA *rsa;
    X509_NAME *name=NULL;
    
    if ((pkeyp == NULL) || (*pkeyp == NULL))
        {
        if ((pk=EVP_PKEY_new()) == NULL)
            {
            abort(); 
            return(0);
            }
        }
    else
        pk= *pkeyp;

    if ((x509p == NULL) || (*x509p == NULL))
        {
        if ((x=X509_new()) == NULL)
            goto err;
        }
    else
        x= *x509p;

    rsa=RSA_generate_key(bits,RSA_F4,callback,NULL);
    if (!EVP_PKEY_assign_RSA(pk,rsa))
        {
        abort();
        goto err;
        }
    rsa=NULL;

    X509_set_version(x,2);
    ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
    X509_gmtime_adj(X509_get_notBefore(x),0);
    X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);
    X509_set_pubkey(x,pk);

    name=X509_get_subject_name(x);

    /* This function creates and adds the entry, working out the
     * correct string type and performing checks on its length.
     * Normally we'd check the return value for errors...
     */
    X509_NAME_add_entry_by_txt(name,"C",
                MBSTRING_ASC, "UK", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name,"CN",
                MBSTRING_ASC, "OpenSSL Group", -1, -1, 0);

    /* Its self signed so set the issuer name to be the same as the
     * subject.
     */
    X509_set_issuer_name(x,name);

    /* Add various extensions: standard extensions */
    add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
    add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");

    add_ext(x, NID_subject_key_identifier, "hash");

    /* Some Netscape specific extensions */
    add_ext(x, NID_netscape_cert_type, "sslCA");

    add_ext(x, NID_netscape_comment, "example comment extension");


#ifdef CUSTOM_EXT
    /* Maybe even add our own extension based on existing */
    {
        int nid;
        nid = OBJ_create("1.2.3.4", "MyAlias", "My Test Alias Extension");
        X509V3_EXT_add_alias(nid, NID_netscape_comment);
        add_ext(x, nid, "example comment alias");
    }
#endif
    
    if (!X509_sign(x,pk,EVP_md5()))
        goto err;

    *x509p=x;
    *pkeyp=pk;
    return(1);
err:
    return(0);
    }

/* Add extension using V3 code: we can set the config file as NULL
 * because we wont reference any other sections.
 */

int add_ext(X509 *cert, int nid, char *value)
    {
    X509_EXTENSION *ex;
    X509V3_CTX ctx;
    /* This sets the 'context' of the extensions. */
    /* No configuration database */
    X509V3_set_ctx_nodb(&ctx);
    /* Issuer and subject certs: both the target since it is self signed,
     * no request and no CRL
     */
    X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
    ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
    if (!ex)
        return 0;

    X509_add_ext(cert,ex,-1);
    X509_EXTENSION_free(ex);
    return 1;
    }
    

在评论中,你说你正在为 OpenSSL 1.1.1 编译。

在 OpenSSL 1 中不再可能直接访问 OpenSSL 结构成员,例如 pkey->pkey.rsa。1.x 之后,因为 OpenSSL 已经不再使用类型化结构指针,而现在使用不透明指针代替。原因是 OpenSSL 结构现在可以在不破坏代码的情况下更改版本之间的布局,这在 1.1.0 之前是不可能的。

根据 OpenSSL 的 wiki:OpenSSL 1.1.0 Changes

All structures in libssl public header files have been removed so that they are "opaque" to library users. You should use the provided accessor functions instead

因此,您现在必须为每个成员使用单独的 getter/setter 函数。在这种情况下,我认为是EVP_PKEY_get1_RSA(),eg:

RSA_print_fp(stdout,EVP_PKEY_get1_RSA(pkey),0);

至于 CRYPTO_mem_leaks(),你确定你是为 OpenSSL 1.1.1 而不是 3.0 编译的吗? CRYPTO_mem_leaks()deprecated in 3.0:

The following functions have been deprecated since OpenSSL 3.0, and can be hidden entirely by defining OPENSSL_API_COMPAT with a suitable version value, see openssl_user_macros(7):

int CRYPTO_mem_leaks(BIO *b);
int CRYPTO_mem_leaks_fp(FILE *fp);
int CRYPTO_mem_leaks_cb(int (*cb)(const char *str, size_t len, void *u),
                        void *u);

int CRYPTO_set_mem_debug(int onoff);
int CRYPTO_mem_ctrl(int mode);
int OPENSSL_mem_debug_push(const char *info);
int OPENSSL_mem_debug_pop(void);
int CRYPTO_mem_debug_push(const char *info, const char *file, int line);
int CRYPTO_mem_debug_pop(void);