OPENSSL使用记录
编译安装
环境:windows11 + msys2 + mingw64
下载openssl-3.3.2并解压到指定目录。
打开 MSYS2 MINGW64 终端,切换到openssl源码目录,注意路径中的'\'需要转换成'/'。
执行以下命令生成Makefile,注意这里要设置prefix为指定目录,否则默认安装到/usr/local根目录中。
bash./Configure --prefix=/usr/local/openssl
执行以下命令编译。
bashmake
执行以下命令安装。
bashmake install
使用OPENSSL生成EC自签名证书
添加OPENSSL bin目录到环境变量,OPENSSL的安装目录位于MSYS2的根目录下的usr/local/openssl。
打开命令行,进入到证书生成目录,执行以下命令生成EC PRIVATE KEY 私钥。
bashopenssl ecparam -name prime256v1 -genkey -noout -out key.pem
目录新建一个名为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
执行以下命令生成证书。
bashopenssl req -new -key key.pem -x509 -nodes -out cert.pem -days 3650 -config openssl.cnf
证书生成完成,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 服务,还需要进一步研究。