gmssl编程之签发X509证书

前言

最近由于项目需求,需要通过代码组装并签发标准X509格式数字证书。故而查询资料对gmssl/openssl中签发证书流程就行了一番研究,终于完成。先记录如下.

命令行实现方式

命令行方式下使用gmssl/openssl指令进行证书签发主要有三步:生成密钥对(私钥)、生成证书请求、签发证书。
具体小伙伴们可参考小编这篇文章:Gmssl生成自签名证书

编程实现方式

同理,编程方式其实就是通过gmssl/openssl库相关接口完成命令行方式的三步。
当然签发用户证书首先需要有CA根证书,因为是测试,所这里小编是通过gmssl命令行方式生成了一对CA根证书(cacert.pem)及CA根私钥(cakey.pem).

step1. 产生密钥对

密钥对与算法有关,看小伙伴们实际需要,gmssl中都有相关的接口去完成该功能。例如:
RSA密钥对

	RSA	 *ret = NULL;
	BIGNUM *bn = NULL;
	unsigned long e = RSA_F4;
	int bits = 2048;
	bn = BN_new();
	ret = RSA_new();
	BN_set_word(bn, e)
	RSA_generate_key_ex(ret, bits, bn, NULL);
	//......

EC密钥对

	EC_KEY *ret = NULL;
	ret = EC_KEY_new_by_curve_name(NID_sm2p256v1);
	EC_KEY_generate_key(ret);
	//......

step2. 生成证书请求

有了密钥对后,接下来需要组装出证书请求,即X509_REQ结构数据。
在这里插入图片描述
生成证书请求主要流程如下:

  1. 创建X509_REQ对象
    通过X509_REQ_new方法实现。
  2. 设置版本号
    版本信息使用X509_REQ_set_version方法进行设置。
  3. 设置使用者信息
    使用者信息可使用X509_REQ_set_subject_name方法直接设置;
    或者通过X509_REQ_get_subject_name先取得X509_NAME,然后使用X509_NAME_add_entry_by_NID方法单独设置X509_NAME中的某一项。
  4. 设置公钥
    对于EC公钥,通过EVP_PKEY_assign_EC_KEY方法将step1中生成的公钥填入到x509_req中;
    对于RSA公钥,通过EVP_PKEY_assign_RSA方法将step1中生成的公钥填入到x509_req中。
  5. 设置扩展项信息(可选)
    扩展项信息可不填;
    若要填写,可通过sk_X509_EXTENSION_new_null、X509V3_EXT_conf、sk_X509_EXTENSION_push、X509_REQ_add_extensions等方法实现。
  6. 设置签名值
    签名值通过X509_REQ_sign方法,使用step1中生成的私钥对x509_req进行签名后自动填入。

step3. 签发X509

签发x509证书即通过step2中的证书请求组装出X509结构证书数据,并使用CA根证书对其进行签名。
在这里插入图片描述
组装并签发证书主要流程如下:

  1. 创建X509对象
    通过X509_new创建X509对象newcert;
  2. 设置版本号
    通过X509_set_version完成设置;
  3. 设置序列号
    通过X509_set_serialNumber方法实现;
  4. 从X509_REQ中导出使用者信息并设置到X509中
    通过X509_REQ_get_subject_name导出使用者信息;
    通过X509_set_subject_name方法设置到newcert中。
  5. 从CA根证书中导出使用者信息并设置到X509的颁发者项中
    通过X509_get_subject_name导出CA根证书中的使用者信息;
    通过X509_set_issuer_name方法设置到newcert的颁发者项中
  6. 从X509_REQ中导出公钥并设置到X509中
    通过X509_REQ_get_pubkey取得证书请求中的公钥;
    通过X509_REQ_verify方法对证书请求中签名值进行验证;(可选)
    通过X509_set_pubkey方法将公钥设置到newcert中;
  7. 设置证书有效期
    通过X509_gmtime_adj设置数值形式(以s为单位)的时间;
    或者通过X509_set1_notBefore及X509_set1_notAfter设置ASN1_TIME格式的时间;
  8. 设置证书扩展项信息(可选)
    扩展项信息根据实际需要来,可不设置。
  9. 设置签名值
    通过X509_sign方法及CA根私钥对newcert进行签名,并将签名值填入到newcert对象中。

完整代码


RSA *gen_RSA()
{
	RSA				*ret = NULL;
	RSA				*rsa = NULL;
	BIGNUM			*bn = NULL;
	unsigned long	e = RSA_F4;
	int				bits = 2048;
	
	if ((bn = BN_new()) == NULL)
	{
		printf("BN_new err\n");
		return NULL;
	}
	if ((ret = RSA_new()) == NULL)
	{
		printf("RSA_new err\n");
		goto END;
	}

	if (!BN_set_word(bn, e) || !RSA_generate_key_ex(ret, bits, bn, NULL))
	{
		printf("BN_set_word or RSA_generate_key_ex err\n");
		goto END;
	}

	ret = rsa;
	rsa = NULL;

END:
	if (bn)
	{
		BN_free(bn);
	}
	if (rsa)
	{
		RSA_free(rsa);
	}
	return ret;
}

EC_KEY *gen_EC_KEY()
{ 
	EC_KEY *ret = NULL;

	///* 获取实现的椭圆曲线个数 */
	//EC_builtin_curve *curves = NULL;
	//int crv_len = 0;
	//int nid = 0;
	//crv_len = EC_get_builtin_curves(NULL, 0);
	//curves = (EC_builtin_curve *)malloc(sizeof(EC_builtin_curve) * crv_len);
	///* 获取椭圆曲线列表 */
	//EC_get_builtin_curves(curves, crv_len);
	//for (int i = 0; i < crv_len; i++) {
	//	printf("***** %d *****\n", i);
	//	printf("nid = %d\n", curves[i].nid);
	//	printf("comment = %s\n", curves[i].comment);
	//}
	///*
	//nid=curves[0].nid;会有错误,原因是密钥太短
	//*/
	///* 选取一种椭圆曲线 */
	//nid = curves[25].nid;

	//根据椭圆曲线参数 创建密钥结构
	if (!(ret = EC_KEY_new_by_curve_name(NID_sm2p256v1))) {
		printf("EC_KEY_new_by_curve_name err!\n");
		return NULL;
	}

	/* 生成密钥 */
	if (!(EC_KEY_generate_key(ret)))
	{
		printf("EC_KEY_generate_key err.\n"); 
		EC_KEY_free(ret);
		return NULL;
	}

	return ret;
}

//
int Add_X509V3_extensions(X509 *cert, X509 * root, 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, root, 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;
}

X509_REQ *generate_X509_REQ()
{
	X509_REQ *ret = NULL;

	BIO          *outbio = NULL;
	EC_KEY       *eckey = NULL;
	X509_REQ     *x509_req = NULL;
	X509_NAME    *x509_name = NULL;
	EVP_PKEY		*pKey = NULL;

	long         lVer = 0L;
	const char   *szCountry = "CA";
	const char	 *szProvince = "HUNAN";
	const char	 *szCity = "ChangSha";
	const char	 *szOrganization = "AHdms";
	const char	 *szOrganizationUnit = "KFB";
	const char	 *szCommon = "localhost";

	/* ---------------------------------------------------------- *
	* Create the Input/Output BIO's.                             *
	* ---------------------------------------------------------- */
	outbio = BIO_new(BIO_s_file());
	outbio = BIO_new_fp(stdout, BIO_NOCLOSE);

	// 1. generate EC key
	eckey = gen_EC_KEY();
	if (eckey == NULL)
	{
		BIO_printf(outbio, "Error generate EC_KEY\n");
		goto free_all;
	}
	
	// create x509_req object
	x509_req = X509_REQ_new();
	if (x509_req == NULL) {
		BIO_printf(outbio, "Error creating new X509_REQ object\n");
		goto free_all;
	}

	// 2. setup version number
	if (!X509_REQ_set_version(x509_req, lVer))
	{
		BIO_printf(outbio, "Error setting version to X509_REQ object\n");
		goto free_all;
	}

	char tmp_buf[512] = { '\0' };
	int tmpBufLen = sizeof(tmp_buf);
	//从CA证书中获取C,ST,O,OU

	// 3. set subject of x509 req
	x509_name = X509_REQ_get_subject_name(x509_req);
	//C
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_countryName, MBSTRING_UTF8, (unsigned char *)szCountry, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_countryName] to X509_REQ object\n");
		goto free_all;
	}
	//ST
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_stateOrProvinceName, MBSTRING_UTF8, (unsigned char *)szProvince, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_stateOrProvinceName] to X509_REQ object\n");
		goto free_all;
	}
	//L
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_localityName, MBSTRING_UTF8, (unsigned char *)szCity, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_localityName] to X509_REQ object\n");
		goto free_all;
	}
	//O
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_organizationName, MBSTRING_UTF8, (unsigned char *)szOrganization, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_organizationName] to X509_REQ object\n");
		goto free_all;
	}
	//OU   OU在openssl.conf中默认是可选的
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_organizationalUnitName, MBSTRING_UTF8, (unsigned char *)szOrganizationUnit, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_organizationalUnitName] to X509_REQ object\n");
		goto free_all;
	}
	//CN
	if (!X509_NAME_add_entry_by_NID(x509_name, NID_commonName, MBSTRING_UTF8, (unsigned char *)szCommon, -1, -1, 0)) {
		BIO_printf(outbio, "Error adding entry [NID_commonName] to X509_REQ object\n");
		goto free_all;
	}

	// 4. set public key of x509 req
	pKey = EVP_PKEY_new();
	if (!EVP_PKEY_assign_EC_KEY(pKey, eckey)) {
		BIO_printf(outbio, "Error EVP_PKEY_assign_EC_KEY operation\n");
		EC_KEY_free(eckey);
		goto free_all;
	}
	eckey = NULL;	// will be free eckey when EVP_PKEY_free(pKey)

	if (1 != (X509_REQ_set_pubkey(x509_req, pKey))){
		BIO_printf(outbio, "Error setting pubkey to X509_REQ object\n");
		goto free_all;
	}

	加入一组可选的扩展属性
	//STACK_OF(X509_EXTENSION) *extlist = sk_X509_EXTENSION_new_null();
	//X509_EXTENSION*ext = X509V3_EXT_conf(NULL, NULL, REQ_SUBJECT_ALT_NAME, value); //生成扩展对象
	//sk_X509_EXTENSION_push(extlist, ext);
	//X509_REQ_add_extensions(x509_req, extlist); // 加入扩展项目。

	// 5. set sign key of x509 req
	int len = X509_REQ_sign(x509_req, pKey, EVP_sm3());	// return x509_req->signature->length
	if (len <= 0){
		unsigned long ulErr = ERR_get_error(); // 获取错误号
		char szErrMsg[1024] = { 0 };
		char *pTmp = NULL;
		pTmp = ERR_error_string(ulErr, szErrMsg); // 格式:error:errId:库:函数:原因
		printf("%s\n", szErrMsg);
		BIO_printf(outbio, "Error sign X509_REQ\n");
		goto free_all;
	}

	ret = x509_req;
	x509_req = NULL;

free_all:
	BIO_free_all(outbio);
	if (pKey)
	{
		EVP_PKEY_free(pKey);
	}
	if (x509_req)
	{
		X509_REQ_free(x509_req);
	}

	return ret;
}


X509 *generate_X509()
{
	X509 *ret = NULL;

	BIO               *outbio = NULL;
	X509_REQ         *certreq = NULL;

	ASN1_INTEGER *aserial = NULL;
	EVP_PKEY *ca_privkey, *req_pubkey;
	X509 *cacert = NULL;
	X509 *newcert = NULL;
	X509_NAME                    *name;
	EVP_MD                       const *digest = NULL;
	FILE                         *fp;
	long                         valid_secs = 31536000;

	/* ---------------------------------------------------------- *
	* Create the Input/Output BIO's.                             *
	* ---------------------------------------------------------- */
	outbio = BIO_new(BIO_s_file());
	outbio = BIO_new_fp(stdout, BIO_NOCLOSE);

	/* ---------------------------------------------------------- *
	* These function calls initialize openssl for correct work.  *
	* ---------------------------------------------------------- */
	OpenSSL_add_all_algorithms();
	ERR_load_BIO_strings();
	ERR_load_crypto_strings();

	/* -------------------------------------------------------- *
	* Load the signing CA Certificate file                    *
	* ---------------------------------------------------------*/
	if (!(fp = fopen(CACERT, "r"))) {
		BIO_printf(outbio, "Error reading CA cert file\n");
		return NULL;
	}

	if (!(cacert = PEM_read_X509(fp, NULL, NULL, NULL))) {
		BIO_printf(outbio, "Error loading CA cert into memory\n");
		fclose(fp);
		return NULL;
	}

	fclose(fp);

	/* -------------------------------------------------------- *
	* Import CA private key file for signing                   *
	* ---------------------------------------------------------*/
	ca_privkey = EVP_PKEY_new();

	if (!(fp = fopen(CAKEY, "r"))) {
		BIO_printf(outbio, "Error reading CA private key file\n");
		goto END;
	}

	if (!(ca_privkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL))) {
		BIO_printf(outbio, "Error importing key content from file\n");
		fclose(fp);
		goto END;
	}

	fclose(fp);

	/* -------------------------------------------------------- *
	* generate x509_req                                        *
	* ---------------------------------------------------------*/
	if (!(certreq = generate_X509_REQ()))
	{
		BIO_printf(outbio, "Error generate X509_REQ\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Build Certificate with data from request                  *
	* ----------------------------------------------------------*/
	if (!(newcert = X509_new())) {
		BIO_printf(outbio, "Error creating new X509 object\n");
		goto END;
	}

	if (X509_set_version(newcert, 2) != 1) {
		BIO_printf(outbio, "Error setting certificate version\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* set the certificate serial number here                    *
	* If there is a problem, the value defaults to '0'          *
	* ----------------------------------------------------------*/
	aserial = ASN1_INTEGER_new();
	ASN1_INTEGER_set(aserial, 0);

	if (!X509_set_serialNumber(newcert, aserial)) {
		BIO_printf(outbio, "Error setting serial number of the certificate\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Extract the subject name from the request                 *
	* ----------------------------------------------------------*/
	if (!(name = X509_REQ_get_subject_name(certreq)))
		BIO_printf(outbio, "Error getting subject from cert request\n");

	/* --------------------------------------------------------- *
	* Set the new certificate subject name                      *
	* ----------------------------------------------------------*/
	if (X509_set_subject_name(newcert, name) != 1) {
		BIO_printf(outbio, "Error setting subject name of certificate\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Extract the subject name from the signing CA cert         *
	* ----------------------------------------------------------*/
	if (!(name = X509_get_subject_name(cacert))) {
		BIO_printf(outbio, "Error getting subject from CA certificate\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Set the new certificate issuer name                       *
	* ----------------------------------------------------------*/
	if (X509_set_issuer_name(newcert, name) != 1) {
		BIO_printf(outbio, "Error setting issuer name of certificate\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Extract the public key data from the request              *
	* ----------------------------------------------------------*/
	if (!(req_pubkey = X509_REQ_get_pubkey(certreq))) {
		BIO_printf(outbio, "Error unpacking public key from request\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Optionally: Use the public key to verify the signature    *
	* ----------------------------------------------------------*/
	if (X509_REQ_verify(certreq, req_pubkey) != 1) {
		BIO_printf(outbio, "Error verifying signature on request\n");
		goto END;
	}

	/* --------------------------------------------------------- *
	* Set the new certificate public key                        *
	* ----------------------------------------------------------*/
	if (X509_set_pubkey(newcert, req_pubkey) != 1) {
		BIO_printf(outbio, "Error setting public key of certificate\n");
		goto END;
	}

	/* ---------------------------------------------------------- *
	* Set X509V3 start date (now) and expiration date (+365 days)*
	* -----------------------------------------------------------*/
	if (!(X509_gmtime_adj(X509_get_notBefore(newcert), 0))) {
		BIO_printf(outbio, "Error setting start time\n");
		goto END;
	}

	if (!(X509_gmtime_adj(X509_get_notAfter(newcert), valid_secs))) {
		BIO_printf(outbio, "Error setting expiration time\n");
		goto END;
	}

	/* ----------------------------------------------------------- *
	* Add X509V3 extensions                                       *
	* ------------------------------------------------------------*/
	//使用者密钥标识
	Add_X509V3_extensions(newcert, cacert, NID_subject_key_identifier, "hash");
	//颁发者密钥标识
	Add_X509V3_extensions(newcert, cacert, NID_authority_key_identifier, "keyid,issuer");
	//密钥用途
	Add_X509V3_extensions(newcert, cacert, NID_key_usage, "Digital Signature, Key Encipherment, Data Encipherment");
	//增强型密钥用途
	Add_X509V3_extensions(newcert, cacert, NID_ext_key_usage, "critical,clientAuth");

	/* ----------------------------------------------------------- *
	* Set digest type, sign new certificate with CA's private key *
	* ------------------------------------------------------------*/
	digest = EVP_sm3();

	if (!X509_sign(newcert, ca_privkey, digest)) {
		BIO_printf(outbio, "Error signing the new certificate\n");
		goto END;
	}

	/* ------------------------------------------------------------ *
	*  print the certificate                                       *
	* -------------------------------------------------------------*/
	if (!PEM_write_bio_X509(outbio, newcert)) {
		BIO_printf(outbio, "Error printing the signed certificate\n");
	}

	ret = newcert;
	newcert = NULL;

END:
	/* ---------------------------------------------------------- *
	* Free up all structures                                     *
	* ---------------------------------------------------------- */
	EVP_PKEY_free(ca_privkey);
	X509_REQ_free(certreq);
	ASN1_INTEGER_free(aserial);
	X509_free(newcert);

	BIO_free_all(outbio);

	return ret;
}

int _tmain(int argc, _TCHAR* argv[])
{
	X509 *cert = generate_X509();
	if (cert != NULL)
	{
		BIO *out = NULL;
		int ret = 0;

		out = BIO_new_file("./newcert.cer", "w");
		ret = PEM_write_bio_X509(out, cert);
		
		BIO_free_all(out);
	}
	
	getchar();
	return 0;
}

运行结果

在这里插入图片描述
OK, 打完收工
————————————————————————————————
我不休息我还能学 ⊂(‘ω’⊂ )))Σ≡=─༄༅༄༅༄༅༄༅༄༅

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值