代码之家  ›  专栏  ›  技术社区  ›  Tim

使用OpenSSL以编程方式创建X509证书

  •  62
  • Tim  · 技术社区  · 17 年前

    我想在应用程序中执行此操作,而不是从命令行。

    什么OpenSSL函数可以为我做到这一点?任何示例代码都是奖励!

    4 回复  |  直到 9 年前
        1
  •  210
  •   Nathan Osman    13 年前

    我意识到这是一个很晚(很长)的答案。但考虑到这个问题在搜索引擎结果中的排名有多高,我想也许值得为它写一个像样的答案。

    下面你会读到的很多东西都是从 this demo


    在实际创建证书之前,我们需要创建私钥。OpenSSL提供了 EVP_PKEY 用于在存储器中存储与算法无关的私钥的结构。此结构在中声明 openssl/evp.h 但包括在 openssl/x509.h

    为了分配 EVP_PKEY_new :

    EVP_PKEY * pkey;
    pkey = EVP_PKEY_new();
    

    还有一个相应的函数用于释放结构- EVP_PKEY_free -它只接受一个参数: 结构已在上面初始化。

    现在我们需要生成一个密钥。对于我们的示例,我们将生成一个RSA密钥。这是通过 RSA_generate_key openssl/rsa.h . 此函数返回指向 RSA 结构

    函数的简单调用可能如下所示:

    RSA * rsa;
    rsa = RSA_generate_key(
        2048,   /* number of bits for the key - 2048 is a sensible value */
        RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
        NULL,   /* callback - can be NULL if we aren't displaying progress */
        NULL    /* callback argument - not needed in this case */
    );
    

    如果 RSA_生成_密钥 NULL ,然后出了点问题。如果没有,那么我们现在有了一个RSA密钥,我们可以将它分配给我们的 执行副总裁

    EVP_PKEY_assign_RSA(pkey, rsa);
    

    这个 RSA 结构被释放。


    现在是证书本身。

    OpenSSL使用 X509 结构来表示内存中的x509证书。此结构的定义在中 openssl/x509.h . 我们需要的第一个功能是 X509_new

    X509 * x509;
    x509 = X509_new();
    

    就像我们的情况一样 执行副总裁 X509_free .

    现在我们需要使用一些 X509_* 功能:

    ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
    

    X509_gmtime_adj(X509_get_notBefore(x509), 0);
    X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
    

    第一行设置证书的 notBefore 属性设置为当前时间。(修订) X509_gmtime_adj 函数将指定的秒数添加到当前时间(在本例中为无)。第二行设置证书的 notAfter

    现在,我们需要使用前面生成的密钥设置证书的公钥:

    X509_set_pubkey(x509, pkey);
    

    由于这是一个自签名证书,我们将颁发者的名称设置为主体的名称。该过程的第一步是获取主题名称:

    X509_NAME * name;
    name = X509_get_subject_name(x509);
    

    X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
                               (unsigned char *)"CA", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
                               (unsigned char *)"MyCompany Inc.", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
                               (unsigned char *)"localhost", -1, -1, 0);
    

    (我在这里使用值'CA',因为我是加拿大人,这是我们的国家代码。还要注意,参数#4需要显式转换为 unsigned char *

    X509_set_issuer_name(x509, name);
    

    最后,我们准备好执行签名过程。我们叫 X509_sign 使用我们先前生成的密钥。这方面的代码非常简单:

    X509_sign(x509, pkey, EVP_sha1());
    

    请注意,我们正在使用 SHA-1 对密钥进行签名的哈希算法。这与 mkcert.c 我在这个答案的开头提到的演示,它使用MD5。


    我们现在有一个自签名证书!但我们还没有完成-我们需要将这些文件写入磁盘。谢天谢地,OpenSSL也用 PEM_* 在中声明的函数 openssl/pem.h . 我们首先需要的是 PEM_write_PrivateKey

    FILE * f;
    f = fopen("key.pem", "wb");
    PEM_write_PrivateKey(
        f,                  /* write the key to the file we've opened */
        pkey,               /* our key from earlier */
        EVP_des_ede3_cbc(), /* default cipher for encrypting the key on disk */
        "replace_me",       /* passphrase required for decrypting the key on disk */
        10,                 /* length of the passphrase string */
        NULL,               /* callback for requesting a password */
        NULL                /* data to pass to the callback */
    );
    

    无效的 chmod 600 key.pem

    唷!现在我们只讨论一个函数——我们需要将证书写入磁盘。我们需要的功能是 PEM_write_X509 :

    FILE * f;
    f = fopen("cert.pem", "wb");
    PEM_write_X509(
        f,   /* write the certificate to the file we've opened */
        x509 /* our certificate */
    );
    

    对于那些有兴趣了解上述所有代码在实际应用程序中的外观的人来说,我提供了一个要点(用C++编写),您可以查看 here .

        2
  •  51
  •   Community Mohan Dere    9 年前

    您首先需要熟悉术语和机制。

    证明书

    在任何情况下,您都需要调用OpenSSL API的20多个不同函数来创建密钥和自签名证书。OpenSSL源代码本身就是一个例子 demos/x509/mkcert.c

    Nathan Osman's explanation 在下面

        3
  •  5
  •   MasterAler Seema Hari    7 年前

    Nathan Osman 解释得非常充分,在C++中有同样的问题需要解决,所以这里是我的小加,CPP风格改写的概念,并考虑了几个注意事项:

    bool generateX509(const std::string& certFileName, const std::string& keyFileName, long daysValid)
    {
        bool result = false;
    
        std::unique_ptr<BIO, void (*)(BIO *)> certFile  { BIO_new_file(certFileName.data(), "wb"), BIO_free_all  };
        std::unique_ptr<BIO, void (*)(BIO *)> keyFile { BIO_new_file(keyFileName.data(), "wb"), BIO_free_all };
    
        if (certFile && keyFile)
        {
            std::unique_ptr<RSA, void (*)(RSA *)> rsa { RSA_new(), RSA_free };
            std::unique_ptr<BIGNUM, void (*)(BIGNUM *)> bn { BN_new(), BN_free };
    
            BN_set_word(bn.get(), RSA_F4);
            int rsa_ok = RSA_generate_key_ex(rsa.get(), RSA_KEY_LENGTH, bn.get(), nullptr);
    
            if (rsa_ok == 1)
            {
                // --- cert generation ---
                std::unique_ptr<X509, void (*)(X509 *)> cert { X509_new(), X509_free };
                std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)> pkey { EVP_PKEY_new(), EVP_PKEY_free};
    
                // The RSA structure will be automatically freed when the EVP_PKEY structure is freed.
                EVP_PKEY_assign(pkey.get(), EVP_PKEY_RSA, reinterpret_cast<char*>(rsa.release()));
                ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1); // serial number
    
                X509_gmtime_adj(X509_get_notBefore(cert), 0); // now
                X509_gmtime_adj(X509_get_notAfter(cert), daysValid * 24 * 3600); // accepts secs
    
                X509_set_pubkey(cert.get(), pkey.get());
    
                // 1 -- X509_NAME may disambig with wincrypt.h
                // 2 -- DO NO FREE the name internal pointer
                X509_name_st* name = X509_get_subject_name(cert.get());
    
                const uchar country[] = "RU";
                const uchar company[] = "MyCompany, PLC";
                const uchar common_name[] = "localhost";
    
                X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, country, -1, -1, 0);
                X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, company, -1, -1, 0);
                X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0);
    
                X509_set_issuer_name(cert.get(), name);
                X509_sign(cert.get(), pkey.get(), EVP_sha256()); // some hash type here
    
    
                int ret  = PEM_write_bio_PrivateKey(keyFile.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
                int ret2 = PEM_write_bio_X509(certFile.get(), cert.get());
    
                result = (ret == 1) && (ret2 == 1); // OpenSSL return codes
            }
        }
    
        return result;
    }
    

    当然,应该有 更多 实际上,检查函数的返回值 全部的

        4
  •  2
  •   Adam Liss    17 年前

    有没有可能通过一个 system 从你的应用程序中呼叫?这样做有几个很好的理由:

    • openssl 可执行文件可以将其与应用程序分开,并可能提供某些优势。 免责声明:就此咨询律师。

    • 文档:OpenSSL随附 非凡的 命令行文档,极大地简化了潜在的复杂工具。

    • 选择权;在你把所有细节都弄清楚之前,你要花大约一天的时间来做这件事。之后,将该命令合并到应用程序中就变得很简单了。

    如果选择使用API,请检查 openssl-dev www.openssl.org上的开发者列表。

    祝你好运