新開源HTML5單文件網頁版ACME客戶端,可在線申請Let’s Encrypt、ZeroSSL免費HTTPS多域名通配符泛域名SSL/TLS證書(RSA/ECC/ECDSA)
之前寫了兩篇關於HTTPS證書的文章,一篇是本地自簽證書,另一篇是Let’s Encrypt開始支援免費通配符證書時通過diafygi/gethttpsforfree
網頁來申請證書,步驟均很繁瑣;於2022年9月我寫了我開發了一個操作非常簡單的網頁版ACME客戶端:ACME-HTML-Web-Browser-Client,並在GitHub、Gitee上進行了開源。
整個源碼僅一個靜態HTML網頁文件,可以直接保存到本地使用,或通過在線網址使用;支援向 Let's Encrypt
、ZeroSSL
等支援 ACME 協議的證書頒發機構,免費申請獲得用於 HTTPS 的 SSL/TLS 域名證書(RSA
、ECC/ECDSA
),支援多域名和通配符泛域名;只需在現代瀏覽器上操作即可獲得 PEM 格式純文本的域名證書,不依賴作業系統環境,無需下載和安裝軟體,無需註冊登錄,純手動操作,只專註於申請獲得證書這一件事。
證書過期風險提醒: 由於本網頁客戶端只能純手動操作,不支援自動續期,需注意在證書過期前重新生申請新證書(免費證書普遍90天有效期,屆時只需重複操作一遍即可),或使用 acme.sh 等客戶端自動化續期。
開源項目的起源
我自己主要是在Windows裡面用的多,並不介意甚至更偏向於純手動操作獲得證書,但官方ACME客戶端列表裡面的能單純提供符合我需求的寥寥無幾:依賴特定作業系統環境、需要安裝特定的運行環境、要自己寫程式碼開發、甚至有的還必須捆綁線上生產環境;而我需求僅僅是單純的想獲得一個證書文件而已,但帶來的是一整套全家桶,很難專註於只申請獲得證書這一件事。
常見的ACME客戶端要麼是需要Bash
腳本環境,要麼就是不同開發語言的源程式碼或者第三方庫需要自己寫程式碼;早先Let’s Encrypt的列表裡面還有提供網頁版的客戶端列表(我在裡面找到的gethttpsforfree
),現在官方因為「一些瀏覽器內 ACME 客戶端可用,但我們未在此處列出它們,因為它們鼓勵手動續訂工作流程,這會導致糟糕的用戶體驗並增加錯過續訂的風險」
而不再展示網頁版客戶端列表,我很失望;也有部分支援Windows的桌面程式可以直接使用,比如win-acme
,不過看了文檔也很難上手;有部分客戶端或網站甚至會要求註冊並登陸賬戶才能使用,還幫你管理私鑰(存儲到他們的伺服器,恨不得把通訊錄都拿走),哈 ~ 呸 ~~
長時間我都是使用fszlin/certes
這個.Net的庫來簽發證書,自己C#寫的一個控制台程式,雙擊程式就能直接到達驗證階段,配置好DNS驗證記錄就能申請到證書文件,但要給別人用就麻煩了,要改C#程式碼;更重要的是EXE程式還有一個信任的問題,雙擊一個EXE程式需要承擔的風險很大;網頁版就完全不同了,網頁本身就帶有很大程度的開放性,我們完全可以審查程式碼是否有加料,網頁天然適合做UI,人機交互的友好性遠超腳本控制台程式,對於用不上自動化續期、或自動化在伺服器進行部署證書的需求,在網頁上進行申請是最理想的操作方式;比如diafygi/gethttpsforfree
我們完全可以看到他的網頁源碼,還能通過瀏覽器控制台完全監控到網路數據流向,最為簡單和可靠,雖然這個網頁也是開源的,但它需要的操作太繁瑣了,早先我還給他提了個我自己在用的簡化簽名操作的方法 issues/164 。
為了能用上一個符合我心裡預期的並且操作簡單的網頁版ACME客戶端,依託於現代瀏覽器對RSA
、ECC
加密、簽名的直接支援,因此我決定自己寫!好在以前積累了點基礎的密碼學知識,編寫本客戶端不存在知識盲點,很順利的寫完了全部HTML程式碼,並進行了開源。
項目地址
GitHub:
//github.com/xiangyuecn/ACME-HTML-Web-Browser-Client
Gitee:
//gitee.com/xiangyuecn/ACME-HTML-Web-Browser-Client
在線使用本客戶端:
中國訪問gitee.io:
//xiangyuecn.gitee.io/acme-html-web-browser-client/ACME-HTML-Web-Browser-Client.html
不太穩定github.io:
//xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html
使用方法
這裡介紹的是完整的操作流程,請根據步驟進行操作,很容易就能申請得到證書文件;本文圖片中顯示的數據都是演示用的並非真實數據。
第一步:選擇Let’s Encrypt、ZeroSSL或其他證書頒發機構
根據上面的項目地址,打開本網頁客戶端(下載保存唯一的這一個html文件並雙擊打開、或者使用在線網址);只要是支援ACME協議的證書頒發機構,都可以在第一步操作中填寫對應地址,目前默認提供了Let's Encrypt
、ZeroSSL
兩家的地址,根據自己需要直接選擇即可。
不同證書頒發機構需要的操作不一定相同,請根據提示進行操作;
Let's Encrypt
直接到第二步操作,ZeroSSL
由於他們的ACME服務對瀏覽器支援很不友好存在跨域問題,需要多操作幾下(複製源碼到他們服務地址頁面中運行,消除跨域問題)。
第二步:證書配置,填寫域名
在網頁的第二步中填寫需要申請證書的域名(支援多域名、通配符),和密鑰等資訊配置;不同證書頒發機構需要填寫的配置不一定相同,請根據提示進行操作。
填寫域名列表: 比如要給你的域名test123.com
申請證書,在域名中可以直接填寫:test123.com, *.test123.com
,帶了一個通配符,這申請出來的證書在test123.com
、www.test123.com
、app.test123.com
等域名都可以使用。注意:如果需要xxx.app.test123.com
這種域名也能使用,需增加填寫*.app.test123.com
類似這種才行。
證書私鑰: 建議直接點擊創建新的RSA私鑰。
ACME賬戶私鑰: 如果你之前申請過,建議填寫之前用過的私鑰,沒有申請過也同樣的創建新的RSA私鑰。
ACME聯繫郵箱: 填寫一個你常用的郵箱,證書頒發機構會在證書到期前給你推送郵件提醒續期,亂填郵箱收不到提醒。
EAB憑據: 有些ACME服務會要求提供外部帳號綁定憑據(External Account Binding
、externalAccountBinding
、externalAccountRequired
),比如ZeroSSL:你可以在ZeroSSL的管理控制台的 Developer 中獲得此憑據,所以你需要先註冊一個ZeroSSL的帳號;Let’s Encrypt沒有此選項。
第三步:完成域名所有權的驗證
根據網頁內顯示的第三步內容,給每個域名配置好合適的域名所有權驗證方式;常見的驗證方式有:DNS解析中配置TXT記錄進行驗證、上傳文件到伺服器進行驗證,通配符域名一般僅支援DNS驗證;等全部配置完成後開始驗證,驗證通過後就能下載得到證書了。
推薦使用DNS驗證方式,只需要登錄你的域名解析管理後台,在域名解析中填寫子域名TXT記錄就行,簡單快捷,並且不容易出錯;比如*.test123.com
,我們需要給子域名_acme-challenge.test123.com
添加一條TXT記錄(這一個子域名下可以添加多條TXT記錄)。
通過上傳文件來驗證,不太推薦這個方式,因為必須要有伺服器,不然ACME訪問不到文件就會驗證不通過;為了讓上傳的文件能正常訪問,可能需要在伺服器內進行一些配置(請自行搜索解決辦法),測試URL地址你自己也能正常訪問後,才能開始驗證。
第四步:下載保存證書PEM文件
等域名所有權驗證通過後,網頁會顯示第四步,提供證書文件下載,下載保存好;有3個文件需要下載(都是純文本,可以用記事本打開):
- 證書文件(含完整證書鏈)
- 證書私鑰文件
- 日誌記錄文件,下次申請時直接拖拽此文件進頁面,自動填寫相同配置
證書文件的後綴.pem
可以改成.crt
或.cer
,這樣在Windows裡面可以直接雙擊打開查看此證書。
最後將下載保存的證書和私鑰配置到你的網站中即可,不同系統程式的配置方法不同,請自行查找對應配置方法。
源碼中一些可以把玩的程式碼
為了在瀏覽器上實現RSA
、ECC
簽名操作,源碼中用純js程式碼實現了:
PKCS#1
的解析,PEM格式公鑰和私鑰PKCS#8
的解析和生成,PEM格式公鑰和私鑰PKCS#10
的生成,PEM格式證書請求CSR文件(Certification Request)
對應的功能封裝在本客戶端內的 X509
對象中,用 X509.CreateCSR 來生成CSR,用 X509.KeyGenerate 來創建PEM格式密鑰,用 X509.KeyParse 來解析PEM格式密鑰,用 X509.KeyExport 來導出PEM格式密鑰。
這些功能都是根據相應的標準用js程式碼在二進位層面上實現的,二進位數據操作封裝在了 ASN1
對象中:實現了 ASN.1 標準的二進位解析和封包,使用 ASN1.ParsePEM 方法可以解析任意的PEM格式密鑰或證書。
請打開本網頁客戶端,在瀏覽器控制台內執行一下程式碼觀賞:
console.log("生成一個512位的RSA私鑰 PKCS#8:");
X509.DefaultType2_RSA="512";
privateKey=await new Promise(function(resolve,reject){
X509.KeyGenerate("RSA", function(pem){resolve(pem)}, function(err){reject(new Error(err))})
});
console.log(privateKey);
console.log("解析一個pem格式密鑰:");
keyInfo=await new Promise(function(resolve,reject){
X509.KeyParse(privateKey, function(info){resolve(info)}, function(err){reject(new Error(err))})
});
console.log(keyInfo);
console.log("導出私鑰的公鑰 PKCS#8:");
publicKey=X509.KeyExport(keyInfo, true);
console.log(publicKey);
console.log("用私鑰創建一個證書請求CSR PKCS#10:");
csr=await new Promise(function(resolve,reject){
X509.CreateCSR(keyInfo, "test123.com"
, ["test.com","*.test.com","*.app.test.com","test123.com","*.test123.com"]
, function(csr){resolve(csr)}, function(err){reject(new Error(err))})
});
console.log(csr);
console.log("用ASN1解析CSR得到二進位結構:");
csrASN1=ASN1.ParsePEM(csr);
console.log(csrASN1);
console.log("用ASN1解析私鑰得到二進位結構:");
privateKeyASN1=ASN1.ParsePEM(privateKey);
console.log(privateKeyASN1);
console.log("用ASN1解析公鑰得到二進位結構:");
publicKeyASN1=ASN1.ParsePEM(publicKey);
console.log(publicKeyASN1);
【END】