Skip to content
目录

OPENSSL使用记录

编译安装

环境:windows11 + msys2 + mingw64

  1. 下载openssl-3.3.2并解压到指定目录。

  2. 打开 MSYS2 MINGW64 终端,切换到openssl源码目录,注意路径中的'\'需要转换成'/'。

  3. 执行以下命令生成Makefile,注意这里要设置prefix为指定目录,否则默认安装到/usr/local根目录中。

    bash
    ./Configure --prefix=/usr/local/openssl
  4. 执行以下命令编译。

    bash
    make
  5. 执行以下命令安装。

    bash
    make install

使用OPENSSL生成EC自签名证书

  1. 添加OPENSSL bin目录到环境变量,OPENSSL的安装目录位于MSYS2的根目录下的usr/local/openssl。

  2. 打开命令行,进入到证书生成目录,执行以下命令生成EC PRIVATE KEY 私钥。

    bash
    openssl ecparam -name prime256v1 -genkey -noout -out key.pem
  3. 目录新建一个名为openssl.cnf的文件,内容如下,可以根据需要修改,可同时设置多个IP和域名。

txt
[req]
default_bits  = 2048
distinguished_name = req_distinguished_name
copy_extensions = copy
req_extensions = req_ext
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
countryName = CN
stateOrProvinceName = GuangDong
localityName = GuangZhou
organizationName = GlowJS
commonName = CA
[req_ext]
basicConstraints = CA:FALSE
subjectAltName = @alt_names
[v3_req]
basicConstraints = CA:FALSE
subjectAltName = @alt_names
[alt_names]
IP.1 = 192.168.0.2
IP.2 = 127.0.0.1
IP.3 = 192.168.10.33
DNS.1 = localhost
DNS.2 = abc.com
  1. 执行以下命令生成证书。

    bash
    openssl req -new -key key.pem -x509 -nodes -out cert.pem -days 3650 -config openssl.cnf
  2. 证书生成完成,key.pem为私钥,cert.pem为证书。此证书可用于 Mongoose HTTPS 服务,浏览器端需要安装cert.pem才不会有警告。

C语言调用OPENSSL库生成EC证书

需要在项目中添加头文件和静态链接库,通过 xmake + mingw64 编译。 xmake.lua 配置如下:

lua
add_rules("mode.debug", "mode.release")

target("cert_test")
    set_kind("binary")
    add_files("src/*.c")
    add_ldflags("-static")
    add_includedirs("include")
    add_links("lib64/libcrypto.a")
    add_links("lib64/libssl.a")
    add_links("ws2_32", "crypt32")
    add_cxflags("-fexec-charset=gbk")

完整示例代码如下:

c
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 初始化并生成 EC 密钥和 EVP_PKEY
int init_and_generate_ec_key(EVP_PKEY **pkey)
{
    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
    if (!pctx)
    {
        fprintf(stderr, "Could not create context for EC key generation.\n");
        return 0;
    }

    // 初始化密钥生成上下文
    if (EVP_PKEY_keygen_init(pctx) <= 0 ||
        EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) <= 0 ||
        EVP_PKEY_keygen(pctx, pkey) <= 0)
    {
        EVP_PKEY_CTX_free(pctx);
        fprintf(stderr, "Could not generate EC private key.\n");
        return 0;
    }

    EVP_PKEY_CTX_free(pctx);
    return 1;
}

// 创建并设置 X509 证书
int create_and_set_x509_cert(X509 **x509_cert, EVP_PKEY *pkey)
{
    *x509_cert = X509_new();
    if (*x509_cert == NULL)
    {
        fprintf(stderr, "Could not create X509 certificate.\n");
        return 0;
    }

    // 设置证书版本为 V3
    if (X509_set_version(*x509_cert, 2) != 1)
    {
        fprintf(stderr, "Could not set X509 version.\n");
        X509_free(*x509_cert);
        return 0;
    }

    // 设置公钥
    if (X509_set_pubkey(*x509_cert, pkey) != 1)
    {
        fprintf(stderr, "Could not set public key.\n");
        X509_free(*x509_cert);
        return 0;
    }

    // 设置证书的有效期
    X509_gmtime_adj(X509_get_notBefore(*x509_cert), 0);
    X509_gmtime_adj(X509_get_notAfter(*x509_cert), 31536000L); // 1年
    return 1;
}

// 添加主题名和备用名称
int add_subject_name_and_alt_name(X509 *x509_cert, char *dns_names[], size_t dns_count, char *ip_addresses[], size_t ip_count)
{
    if (x509_cert == NULL)
    {
        fprintf(stderr, "Invalid X509 certificate pointer.\n");
        return 0;
    }

    // 添加主题名
    X509_NAME *name = X509_get_subject_name(x509_cert);
    if (!X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0) ||
        !X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"MyCompany", -1, -1, 0) ||
        !X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"mycert.com", -1, -1, 0))
    {
        fprintf(stderr, "Failed to add subject name entries.\n");
        return 0;
    }

    // 创建备用名称的堆栈
    STACK_OF(GENERAL_NAME) *san_names = sk_GENERAL_NAME_new_null();
    if (san_names == NULL)
    {
        fprintf(stderr, "Failed to create stack of GENERAL_NAME.\n");
        return 0;
    }

    // 添加 DNS 名称
    for (size_t i = 0; i < dns_count; i++)
    {
        GENERAL_NAME *gn = GENERAL_NAME_new();
        if (gn == NULL)
        {
            fprintf(stderr, "Failed to create GENERAL_NAME.\n");
            sk_GENERAL_NAME_free(san_names);
            return 0;
        }
        GENERAL_NAME_set0_value(gn, GEN_DNS, (void *)dns_names[i]);
        sk_GENERAL_NAME_push(san_names, gn);
    }

    // 添加 IP 地址
    for (size_t i = 0; i < ip_count; i++)
    {
        GENERAL_NAME *gn = GENERAL_NAME_new();
        if (gn == NULL)
        {
            fprintf(stderr, "Failed to create GENERAL_NAME.\n");
            sk_GENERAL_NAME_free(san_names);
            return 0;
        }
        unsigned char ip_address[4];
        sscanf(ip_addresses[i], "%hhu.%hhu.%hhu.%hhu", &ip_address[0], &ip_address[1], &ip_address[2], &ip_address[3]);
        GENERAL_NAME_set0_value(gn, GEN_IPADD, (void *)ip_address);
        sk_GENERAL_NAME_push(san_names, gn);
    }

    // 将备用名称添加到证书中
    char *san_str = NULL;

    // 计算需要的缓冲区大小
    size_t size = 0;
    size += snprintf(NULL, 0, "DNS:%s", dns_names[0]); // 假设至少有一个 DNS 名称
    for (size_t i = 1; i < dns_count; i++)
    {
        size += snprintf(NULL, 0, ",DNS:%s", dns_names[i]);
    }
    for (size_t i = 0; i < ip_count; i++)
    {
        size += snprintf(NULL, 0, ",IP:%s", ip_addresses[i]);
    }

    san_str = (char *)malloc(size + 1); // +1 for the null terminator
    if (san_str == NULL)
    {
        fprintf(stderr, "Failed to allocate memory for subjectAltName string.\n");
        sk_GENERAL_NAME_free(san_names);
        return 0;
    }

    snprintf(san_str, size + 1, "DNS:%s", dns_names[0]);
    for (size_t i = 1; i < dns_count; i++)
    {
        snprintf(san_str + strlen(san_str), size - strlen(san_str) + 1, ",DNS:%s", dns_names[i]);
    }
    for (size_t i = 0; i < ip_count; i++)
    {
        snprintf(san_str + strlen(san_str), size - strlen(san_str) + 1, ",IP:%s", ip_addresses[i]);
    }

    // 创建 subjectAltName 扩展并添加到证书中
    X509_EXTENSION *ext = X509V3_EXT_conf(NULL, NULL, "subjectAltName", san_str);
    if (!ext || !X509_add_ext(x509_cert, ext, -1))
    {
        fprintf(stderr, "Failed to add subjectAltName extension to certificate.\n");
        sk_GENERAL_NAME_free(san_names); // 发生错误,释放堆栈
        X509_EXTENSION_free(ext);
        free(san_str);
        return 0;
    }

    sk_GENERAL_NAME_free(san_names); // 释放堆栈
    X509_EXTENSION_free(ext);        // 释放扩展
    free(san_str);                   // 释放分配的字符串

    return 1;
}

// 签名 X509 证书,并将私钥与证书保存到文件
int sign_cert_and_save_to_files(X509 *x509_cert, EVP_PKEY *pkey, const char *priv_key_file, const char *cert_file)
{
    X509_set_issuer_name(x509_cert, X509_get_subject_name(x509_cert)); // 自签名证书,发行者与主题相同

    // 使用私钥对证书进行签名
    if (!X509_sign(x509_cert, pkey, EVP_sha256()))
    {
        fprintf(stderr, "证书签名失败。\n");
        return 0;
    }

    // 将私钥保存到文件
    FILE *priv_key_fp = fopen(priv_key_file, "wb");
    if (priv_key_fp == NULL)
    {
        fprintf(stderr, "无法打开私钥文件以进行写入。\n");
        return 0;
    }

    // 使用 PEM_write_ECPrivateKey 写入 EC 私钥
    if (PEM_write_ECPrivateKey(priv_key_fp, EVP_PKEY_get0_EC_KEY(pkey), NULL, NULL, 0, NULL, NULL) != 1)
    {
        fprintf(stderr, "无法写入私钥文件。\n");
        fclose(priv_key_fp);
        return 0;
    }
    fclose(priv_key_fp);

    // 将证书保存到文件
    FILE *cert_fp = fopen(cert_file, "wb");
    if (cert_fp == NULL)
    {
        fprintf(stderr, "无法打开证书文件以进行写入。\n");
        return 0;
    }
    PEM_write_X509(cert_fp, x509_cert);
    fclose(cert_fp);

    return 1;
}

// 创建证书的封装函数
int create_cert(const char *priv_key_file, const char *cert_file, char *dns_names[], size_t dns_count, char *ip_addresses[], size_t ip_count)
{
    EVP_PKEY *pkey = NULL;
    X509 *x509_cert = NULL;

    // 初始化并生成 EC 密钥
    if (!init_and_generate_ec_key(&pkey))
    {
        fprintf(stderr, "初始化并生成 EC 密钥失败。\n");
        return 0;
    }

    // 创建并设置 X509 证书
    if (!create_and_set_x509_cert(&x509_cert, pkey))
    {
        fprintf(stderr, "创建并设置 X509 证书失败。\n");
        EVP_PKEY_free(pkey);
        return 0;
    }

    // 添加主题名和备用名称
    if (!add_subject_name_and_alt_name(x509_cert, dns_names, dns_count, ip_addresses, ip_count))
    {
        fprintf(stderr, "添加主题名和备用名称失败。\n");
        X509_free(x509_cert);
        EVP_PKEY_free(pkey);
        return 0;
    }

    // 签名证书并将私钥和证书保存到文件
    if (!sign_cert_and_save_to_files(x509_cert, pkey, priv_key_file, cert_file))
    {
        fprintf(stderr, "签名证书或保存文件失败。\n");
        X509_free(x509_cert);
        EVP_PKEY_free(pkey);
        return 0;
    }

    printf("证书和私钥已成功保存到文件。\n");

    // 清理资源
    X509_free(x509_cert);
    EVP_PKEY_free(pkey);

    return 1;
}

int main()
{
    // 准备域名和 IP 地址数组
    char *dns_names[] = {"localhost", "xxx.com"};
    char *ip_addresses[] = {"127.0.0.1", "192.168.0.2"}; // 需使用字符串表示的 IP 地址
    size_t dns_count = sizeof(dns_names) / sizeof(dns_names[0]);
    size_t ip_count = sizeof(ip_addresses) / sizeof(ip_addresses[0]);

    const char *priv_key_file = "key.pem";
    const char *cert_file = "cert.pem";

    // 创建证书
    if (!create_cert(priv_key_file, cert_file, dns_names, dns_count, ip_addresses, ip_count))
    {
        fprintf(stderr, "创建证书失败。\n");
        return 1;
    }

    return 0;
}

代码能正常编译,但是有警告,需要进一步处理。能正常运行并生成证书。但是生成的证书无法用到 Mongoose HTTPS 服务,还需要进一步研究。

技术支持:13352865103(柯工,微信同号);18688783852(柯工)