证书请求简介
文章目錄
- 前言與摘要
- 背景要求
- 摘要
- openssl命令行-生成證書請求
- RFC - 證書請求的消息格式( Certificate Request Message Format)
- openssl源碼閱讀-生成證書請求
- 調用openssl api - 生成證書請求
- 附錄
- RFC文件的查看方法
前言與摘要
背景要求
- 證書申請的基本原理:可以參考《圖解密碼技術》-- 結城浩 – 第十章 證書。
- 了解基本的openssl 命令行使用。
摘要
本文沒有介紹證書申請的背景知識和相關的openssl使用。本文直接開始介紹,使用openssl命令,生成證書請求。為了搞明白證書請求需要包含哪些內容,我們去閱讀了證書請求的相關RFC規范。接著,我們閱讀openssl命令執行證書請求的源碼執行流程。閱讀源碼的過程中,我們淺嘗輒止,沒有深入閱讀具體的函數實現。所以,我們不知道,在證書申請這塊,openssl是否完全遵守了RFC規范。最后,我們使用C++封裝了openssl api,實現了一個簡單的證書請求類。
openssl命令行-生成證書請求
openssl req :req - PKCS#10 certificate request and certificate generating utility.
openssl-genrsa:genrsa - generate an RSA private key.
我們使用genrsa生成臨時RSA密鑰。接著,我們使用req生成證書請求。關于命令使用的詳細介紹,參見上面兩個官方文檔鏈接。
# 生成一個私鑰 ? openssl genrsa -out private_key.pem 2048 # pem格式# 交互式的構造證書請求 ? openssl req -key private_key.pem -new -out cert_req.pem我個人更喜歡使用配置文件生成證書請求。下面內容,保存在ruler.conf中。(簡單起見,我注釋掉了擴展項部分)
[req] prompt = no # 如果設置為值 no 這將禁用證書字段的提示,并且直接從配置文件中獲取值。 utf8 = yes distinguished_name = req_distinguished_name # req_extensions = v3_req[req_distinguished_name] organizationName = galactic alliance emailAddress = unknow@haha.gal commonName = ruler of earth# [v3_req] # basicConstraints = critical, CA:false接下來,我們使用openssl命令行,生成證書請求。證書請求輸出在ruler_csr.pem文件中。
? openssl req -key private_key.pem -new -config ruler.conf -out ruler_csr.pem我們來解析證書請求,看下ruler_csr.pem文件中,有哪些內容。
? openssl req -in ruler_csr.pem -text -noout Certificate Request:Data:Version: 1 (0x0)Subject: O = galactic alliance, emailAddress = unknow@haha.gal, CN = ruler of earthSubject Public Key Info:Public Key Algorithm: rsaEncryptionRSA Public-Key: (2048 bit)Modulus:00:ad:29:30:ec:c7:26:ae:69:89:68:81:d5:76:c6:7e:af:f9:32:b8:d1:03:f1:ed:a6:5f:fa:3a:f2:a6:bf:39:6a:61:e4:6a:cc:1b:12:b5:ea:f9:ef:4f:2f:43:6a:78:c3:62:39:8c:26:b1:ac:1d:6f:66:60:32:8d:4d:53:6f:98:5c:f2:04:2c:2a:78:bf:74:29:7a:5c:99:2a:c1:6f:fe:07:c2:d7:7a:bf:a2:73:22:29:db:45:3e:dc:5d:e8:22:d6:4a:81:7d:fa:d5:be:bd:43:09:e9:0a:63:a9:e7:62:9f:cc:67:a4:39:38:61:ed:f8:b9:79:1c:1c:b0:d9:47:7b:4c:1e:57:17:89:8a:9a:d0:4e:da:e8:47:fe:87:db:6c:1f:34:2e:2f:77:d2:7a:76:ed:c3:be:ef:a2:0a:23:72:9e:44:31:8c:a6:4f:bc:93:48:42:cd:c2:05:db:4e:ab:a3:74:84:9e:3f:73:04:f5:da:de:ab:95:d3:ce:30:a5:1d:4b:3a:90:85:b6:5e:e6:b2:0b:a9:33:91:33:b6:f5:24:e9:f4:3a:ba:33:4d:8f:92:74:15:76:f8:23:ca:f3:c0:0b:71:a3:fc:50:50:49:06:ee:9a:b6:57:4e:dd:ac:ee:f7:cf:9b:0d:52:21:24:38:ab:62:1c:e8:b1:7fExponent: 65537 (0x10001)Attributes:a0:00Signature Algorithm: sha256WithRSAEncryption89:29:6e:ee:3a:00:bd:e9:0a:c0:31:c8:3d:96:a5:38:88:18:e0:7d:31:84:a2:ea:e0:fa:bd:03:9a:4a:58:9a:28:c8:02:a6:50:02:03:d1:25:23:a3:16:14:5b:d4:8d:48:32:95:48:0d:55:38:88:f4:d6:bf:f4:1e:e7:c2:7c:e6:4b:df:34:ce:2c:82:58:6e:0c:52:8e:d5:d1:d0:cc:06:61:84:9e:d1:19:8b:07:9b:ab:75:b8:e9:bc:a4:ca:bd:e1:9d:ab:cf:ad:eb:6d:2b:a0:90:b7:0b:33:6f:bc:a2:13:36:d7:1a:d7:e9:7d:f0:1f:63:93:1a:e9:c5:d1:5c:f4:1f:7e:ce:e6:c4:95:77:2b:d7:68:c2:f0:53:23:e2:10:ae:d4:54:0f:f1:89:a4:7a:dd:78:49:ad:d0:7f:19:de:ee:e1:ee:87:f0:91:4b:53:f8:99:2f:d9:20:30:a9:52:95:6e:f0:7a:c6:81:1c:e3:04:33:5d:b0:d0:4f:ca:38:82:d5:35:59:49:4c:16:9e:ff:65:d7:8c:c3:a7:da:b3:9f:07:8c:6d:b1:a1:b9:e1:2f:42:6f:2c:2e:91:cb:c4:3a:61:7b:4c:5d:05:47:e5:76:a6:fe:b4:d8:10:b0:11:91:d7:10:6a:39:b7:0c:a4:e3:56:06:d4:65:bb根據縮進,我們可以看到Certificate Request中包含兩部分內容:Data和Signature。
Data中包含Subject和public key信息。Signature為摘要之后的簽名。
此時,我們懷揣一個疑問:證書請求應該包含哪些內容,它的規范是什么?
下一節,我們來嘗試解決這個問題。
RFC - 證書請求的消息格式( Certificate Request Message Format)
需要翻閱如下的RFC文檔。關于如何查找這些文檔,見“附錄-RFC文件的查看方法”。
- RFC 2511 [英文 | 中文]
- RFC 4211 [英文 | 中文]
- RFC 9045 [英文]
雖然這三個RFC文檔,我都翻了一遍。但我并沒有看懂。詳見上面鏈接。
哈哈,但問題不大。最起碼知道,證書申請包含三部分內容:證書請求信息、證明證書主體的實體實際擁有相應私鑰,證書請求上下文相關的補充信息。
CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsgCertReqMsg ::= SEQUENCE {certReq CertRequest,popo ProofOfPossession OPTIONAL,-- content depends upon key typeregInfo SEQUENCE SIZE(1..MAX) of AttributeTypeAndValue OPTIONAL}certReq:由請求標識符、證書內容模板和可選的控制信息序列組成。(上一節中的subject和publicKey均為證書模板內容中的一部分)
popo:需要證明請求證書有對應的私鑰。(上一節中這一部分是簽名值)
regInfo:僅包含與證書請求上下文相關的補充信息。(不知道這啥東西)
那有個問題:openssl的實現是否滿足上面規范,或者說,openssl如何實現上面規范。
這個問題跳過。下面雖然閱讀了點openssl源碼,但具體的函數內部沒有去閱讀。
openssl源碼閱讀-生成證書請求
調試下面命令對應openssl源碼的執行過程。
openssl req -key private_key.pem -new -config ruler.conf -out ruler_csr.pem# 調試下面命令會失敗。因為如果沒有指定配置文件,會加載默認配置文件openssl.conf。 # 而我沒有源碼安裝openssl,所以會報錯。 openssl req -new -sha256 -key private_key.pem -utf8 -subj /O=galactic alliance/emailAddress=unknow@haha.gal/CN=ruler of earth -out cert_req.pem-
進入命令行指定的程序req:
- pname = opt_progname(argv[0]);獲取程序名。
- do_cmd(prog, argc, argv);進入統一跳轉函數。
- fp = lh_FUNCTION_retrieve(prog, &f);、return fp->func(argc, argv);進入req_main函數。
-
循環獲取參數:while ((o = opt_next()) != OPT_EOF)
-
根據參數進行處理:
- pkey = load_key(keyfile, keyform, 0, passin, e, "private key");加載私鑰
- …
-
生成證書申請。
- req = X509_REQ_new_ex(app_get0_libctx(), app_get0_propq());。創建證書申請空間。
- make_REQ(req, pkey, fsubj, multirdn, !gen_x509, chtype)。填充證書。pkey為私鑰;fsbj為X509_NAME格式,里面為subject信息。
- X509_REQ_set_version(req, X509_REQ_VERSION_1)設置版本號。
- X509_REQ_set_subject_name(req, fsubj);設置主題信息。
- X509_REQ_set_pubkey(req, pkey)設置公鑰。
- PEM_write_bio_X509(out, new_x509);默認為pem格式輸出。
- X509_REQ_free(req);,X509_NAME_free(fsubj);``EVP_PKEY_free(pkey);釋放空間。
-
此次調試沒有進過parse_name函數。但由于下一節我自己寫了一個簡化版的這個函數。所以,還是看下這個函數。當調用-subj /type0=value0/type1=value1/type2=...,會觸發這個函數 – 使用命令行的信息填充subject。
- n = X509_NAME_new();開辟X509_NAME格式空間。
- nid = OBJ_txt2nid(typestr);,X509_NAME_add_entry_by_NID(n, nid, chtype, valstr, strlen((char *)valstr), -1, ismulti ? -1 : 0))添加subject條目。
調用openssl api - 生成證書請求
參考自:《Openssl 編程》-- 趙春平 – 第二十五章 證書申請、OpenSSL中文手冊 – 藍月心語 – OpenSSL中文手冊之X509庫詳解(未完待續)
下面為簡略代碼,刪除了調用函數的返回值檢查。需要用的時候,自行添加。
#pragma once#include "exception.hpp" #include "load_key.hpp"#include <openssl/x509.h> #include <openssl/err.h> #include <boost/utility/string_ref.hpp> #include <iostream> #include <sstream> #include <string>class req { private:X509_REQ* x509_req = nullptr;char ERR[1024] = {0};X509_NAME* get_subject(const std::string subject_contents); // 通過字符串subject_contents,生成X509_REQ結構public:req(const std::string digest_name, const std::string private_key_path, const std::string subject_contents);void out_to_file(int out_format, const std::string out_path); //證書申請輸出到文件void out_to_stdout(int out_format); //證書申請輸出到標準輸出~req(); };req::req(const std::string digest_name, const std::string private_key_path, const std::string subject_contents) {// 填充版本號, 主題名,公鑰, 摘要并私鑰簽名x509_req = X509_REQ_new();// 0對應版本1;// 1對應的是版本2;2對應版本3;long version = 0;X509_REQ_set_version(x509_req, version);X509_NAME* subject_name = get_subject(subject_contents);EVP_PKEY* pkey = load_private_key(private_key_path, _FORMAT_UNDEF);X509_REQ_set_pubkey(x509_req, pkey);const EVP_MD* md = EVP_get_digestbyname(digest_name.c_str());X509_REQ_sign(x509_req, pkey, md);// 釋放不需要的資源X509_NAME_free(subject_name);EVP_PKEY_free(pkey); }X509_NAME* req::get_subject(const std::string subject_contents) {// 通過字符串subject,生成X509_REQ結構X509_NAME* subject_name = X509_NAME_new();std::istringstream ins(subject_contents);std::string tmp;while(std::getline(ins,tmp,'/')) {std::string::size_type equal_sin_loc = tmp.find('=');if(equal_sin_loc == std::string::npos) {BOOST_THROW_EXCEPTION(arg_err()<<err_str("cant find = in subject agrs"));}std::string key = tmp.substr(0, equal_sin_loc);std::string value = tmp.substr(equal_sin_loc+1, tmp.length()- equal_sin_loc -1);// https://www.openssl.org/docs/manmaster/man3/X509_NAME_add_entry.html// For almost all applications loc can be set to -1 and set to 0. if(key == "C") {X509_NAME_add_entry_by_txt(subject_name, "countryName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else if(key == "ST") {X509_NAME_add_entry_by_txt(subject_name, "stateOrProvinceName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else if(key == "L") {X509_NAME_add_entry_by_txt(subject_name, "localityName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else if(key == "O") {X509_NAME_add_entry_by_txt(subject_name, "organizationName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0); } else if(key == "OU") {X509_NAME_add_entry_by_txt(subject_name, "organizationalUnitName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else if(key == "EMAIL") {X509_NAME_add_entry_by_txt(subject_name, "emailAddress", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else if(key == "CN") {X509_NAME_add_entry_by_txt(subject_name, "commonName", MBSTRING_UTF8, (const unsigned char*)value.c_str(), value.length(), -1, 0);} else {BOOST_THROW_EXCEPTION(arg_err()<<err_str("Illegal name appears in parameter. The legal names are follow: C/ST/L/O/OU/EMAIL/CN"));}}return subject_name; }void req::out_to_file(int out_format, const std::string out_path) {// 將證書申請,寫入文件BIO* out = BIO_new_file(out_path.c_str(), "w");if(out_format == _PEM) {PEM_write_bio_X509_REQ(out, x509_req);}else if(out_format == _DER) {i2d_X509_REQ_bio(out,x509_req);}BIO_free(out); }void req::out_to_stdout(int out_format) {// 將證書申請,寫入文件BIO* out = BIO_new_fp(stdout, BIO_NOCLOSE);if(out_format == _PEM) {PEM_write_bio_X509_REQ(out, x509_req);}else if(out_format == _DER) {i2d_X509_REQ_bio(out,x509_req);}BIO_free(out); }req::~req() {X509_REQ_free(x509_req); }附錄
RFC文件的查看方法
RFC-wiki中有一篇博客鏈接:[譯] 如何閱讀 RFC 文檔。這篇博客給出了在RFC Editor查找需要的RFC文檔。
以本篇博客為例,我想查找證書請求的RFC文檔,可以得到如下結果。
RFC 2511 中規定的證書請求消息格式已經被RFC 4211淘汰。RFC 4211 中使用算法,在RFC 9045中有更新。
所以想了解證書的請求規范,需要翻閱:RFC 2511、RFC 4211、RFC 9045。
我不喜歡直接讀英文文檔,所以我去找翻譯。
可以在這四個站點,找到大多數RFC的文檔翻譯:http://rfc.ac.cn/、https://rfc2cn.com/、https://docs.huihoo.com/rfc/、http://www.cnpaf.net/Class/RFC/
所以本篇博客需要閱讀的RFC的鏈接如下:
- RFC 2511 [英文 | 中文]
- RFC 4211 [英文 | 中文]
- RFC 9045 [英文]
總結
- 上一篇: android 服务器证书校验,Andr
- 下一篇: “玲珑杯”郑州轻工业学院第九届ACM程序