# 如何在 windows 上建立 SSL 憑證 ###### tags: `w3HexSchool` . `ssl` :::info ## 2020-12-15 更新 certbot 已支援 windows 上設定 ssl 詳情請見 ==[官方網站](https://certbot.eff.org/lets-encrypt/windows-nginx)== ::: > 最近需要在 windows 上使用 https , 遇到 https 想當然要設定 SSL 憑證啦! 🧐 > 以前都馬在 Linux 上使用 certbot , 真是非常 happy 😆 > 今天要在 windows 上安裝 SSL 憑證 , > 結果發現 certbot 不支援 windows 😱 , > 讓我們來看看在 windows 上可以如何來處理這該死的 SSL 憑證吧 !😠 膜拜完 Google 大神後 , 發現有 3 種方式可行 - 建立不可信任的憑證 , 我們可以使用 [selfsigned](https://www.npmjs.com/package/selfsigned) 產生 - 如果要一個可信任憑證的話 , 可用 [zerossl](https://app.zerossl.com/certificates/draft) 網站產生 - 或是用 [Crypt-LE](https://github.com/do-know/Crypt-LE#for-windows) 的 le64.exe 來產生 - [2020-12-15 更新] 使用 [certbot](https://certbot.eff.org/lets-encrypt/windows-nginx) 產生 SSL 憑證 ### 建立測試用的憑證 - [selfsigned](https://www.npmjs.com/package/selfsigned) 安裝 selfsigned ```shell= npm i -s selfsigned ``` 建立 generate.js 檔案 , 執行之 ```javascript= // generate.js const selfsigned = require('selfsigned'); const domain_names = 'localhost,sit.ttfb.com.tw'; // 不同的 domain_name 用 , 做區隔 const attrs = [{name: 'commonName', value: domain_names}]; const pems = selfsigned.generate(attrs, {days: 365}); console.log(pems); const fs = require('fs'); // 下方會將檔案存到與 js 同層的位置 fs.writeFileSync('./key.pem', pems.private, 'utf8'); fs.writeFileSync('./cert.pem', pems.cert, 'utf8'); fs.writeFileSync('./.fingerprint', pems.fingerprint, 'utf8'); fs.writeFileSync('./.public.pub', pems.public, 'utf8'); ``` 執行後會產生 4 個檔案 ![](https://i.imgur.com/RFrfYa3.png) 讓我們用 https 模組 , 幫我們確認 SSL cert 產生的狀況吧 ! ```javascript= // server.js const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('./key.pem'), cert: fs.readFileSync('./cert.pem') }; https.createServer(options, function (req, res) { res.end('secure!'); }).listen(443); // Redirect from http port 80 to https const http = require('http'); http.createServer(function (req, res) { console.log('req.headers=', req.headers); res.writeHead(301, {"Location": "https://" + req.headers['host'] + req.url}); res.end(); }).listen(80, () => console.log('http start well')); ``` 然後用瀏覽器訪問 https://localhost 可看到以下畫面 ![](https://i.imgur.com/ZjDNokq.png) ### 利用網頁服務建立可信任的憑證 - [zerossl](https://app.zerossl.com/certificates/draft) Dashboard -> New Certificates ![](https://i.imgur.com/D9Zw2u6.png) 輸入 Domains -> Next Step (網頁會自動做 nslookup 的檢查 , 所以要確保 nslookup 在 8.8.8.8 上查的到) ![](https://i.imgur.com/shjlUvc.png) 使用 90 天的 SSL 憑證 -> Next Step ![](https://i.imgur.com/E5kOti7.png) 自動生成 CSR 檔 -> Next Step ![](https://i.imgur.com/nZJgwqR.png) 使用免費的 ( Free ) -> Next Step ![](https://i.imgur.com/JcFc3pK.png) 利用 Http File Upload 作網站所有權驗證 ( 我們需要向網站 zerossl 證明 Domain 是我們的 , 它才能派發可信任的 SSL 憑證給我們 < 此機制可防止駭客仿冒憑證 > ) ![](https://i.imgur.com/f2n6WGw.png) 下載 Auth File , `771957028E5EC614768BB40D0AD8C09E.txt` ![](https://i.imgur.com/stoZyq7.png) 使用 express.js 的 app.get 輸出 Auth File 的 txt 內容 ```javascript= // app.js const fs = require('fs'); const express = require('express'); const app = express(); const port = 80; const file_name = '771957028E5EC614768BB40D0AD8C09E.txt'; app.get(`/.well-known/pki-validation/${file_name}`, (req, res) => { const txt = fs.readFileSync(`./auth/${file_name}`).toString(); res.setHeader('content-type', 'text/plain'); res.send(txt); }); const server = app.listen(port, () => { console.log(`express.js start at http://localhost:${server.address().port}`); }); ``` 需要將 `771957028E5EC614768BB40D0AD8C09E.txt` 放入 auth 資料夾中 ``` // 資料夾結構 ----- |---- app.js |---- auth |----- ???.txt ``` 然後用瀏覽器查看 http://stg.thaitown.andrewdeveloper.com/.well-known/pki-validation/771957028E5EC614768BB40D0AD8C09E.txt 確認 txt 內容是否可訪問到 txt 檔的內容 ![](https://i.imgur.com/eauNXFG.png) 如果可看到 Auth File 的內容 , 按下 Next Step , 接著去 Verify Domain 了! ![](https://i.imgur.com/WrZ54Z2.png) 驗證成功後 , 會出現下方畫面 , 之後就可以下載 SSL cert 檔 ![](https://i.imgur.com/IBmCwnB.png) 選擇 nginx -> Download Certificate , 下載檔案 ![](https://i.imgur.com/Be9TpYB.png) ![](https://i.imgur.com/DrVXQoZ.png) 將下載的檔案 , 解壓縮到 cert 資料夾 ![](https://i.imgur.com/6cMChOw.png) 建立 server.js 在 ssl 資料夾當作 http 與 https 的伺服器 , 用於驗證下載的 SSL 憑證是正常可用的受信任憑證 ```javascript= // server.js const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('./cert/private.key'), cert: fs.readFileSync('./cert/certificate.crt') }; https.createServer(options, function (req, res) { res.end('secure!'); }).listen(443); const http = require('http'); http.createServer(function (req, res) { // Redirect from http to https res.writeHead(301, {"Location": "https://" + req.headers['host'] + req.url}); res.end(); }).listen(80, () => console.log('http start well')); ``` 然後用瀏覽器訪問 https://stg.thaitown.andrewdeveloper.com/ ![](https://i.imgur.com/dSCCtge.png) 這樣我們就成功設定 SSL 憑證了 ! ( P.S. 這個憑證只 3 個月有效 , 會失效的別忘了更新歐 ) ### 利用 exe 檔建立可信任的憑證 - [Crypt-LE](https://github.com/do-know/Crypt-LE#for-windows) 由於需要 public host_name 所以我們使用 aws 上的 windows 伺服器作測試 #### 開啟 80 port 讓外部可以訪問 請參考 : https://stackoverflow.com/questions/56456926/aws-windows-server-hosting-ports-other-than-80-not-working #### 下載 le64.exe 下載位置 : https://github.com/do-know/Crypt-LE/releases 將下載的 le64.zip 解壓縮到桌面的 ssl 資料夾中 , 可看到檔案 le64.exe , 這就是我們之後要使用的工具 ![](https://i.imgur.com/jwiZWLo.png) #### 準備執行的 script 在 ssl 資料夾中 , 建立一個 add-ssl-script.txt 文字檔 , 並輸入下方文字於其中 ( 記得要改 [your_email] . [target_domain_name] 這兩個參數歐 ! ) ```shell= le64.exe -email "[your_email]" -key account.key -csr domain.csr -csr-key domain.key -crt domain.crt -domains "[target_domain_name]" -generate-missing -live ``` ![](https://i.imgur.com/EP7RYNh.png) 將 add-ssl-script.txt 的副檔名改成 .bat , 然後執行它 ![](https://i.imgur.com/RlCe7AC.png) `add-ssl-script.bat` 執行中 , 如果卡住的話 , 可按 enter 讓其執行下一動作 ![](https://i.imgur.com/AjzU7Gp.png) 如果出現 ==Error requesting certificate== 代表此次建立 cert 失敗 ! ![](https://i.imgur.com/s8GSYyq.png) 驗證的其中一個步驟會要求您將指定的 Url 放上 le64 隨機生成的文字 ![](https://i.imgur.com/AjzU7Gp.png) 根據上方圖片將 url : `http://[your_host]/.well-known/acme-challenge/VCxJsc-la448P9QmVB6TTHFBjwQ0O3NSPE2WNL8tPf4` 回應特定文字 `VCxJsc-la448P9QmVB6TTHFBjwQ0O3NSPE2WNL8tPf4.I7C2KmyCB_pAhWSyEve1U2ulMwJJ7zIAMDjFSFx34io` ![](https://i.imgur.com/LioiAMt.png) 驗證成功後 , 可看到 `The job is done, enjoy your certificate!` ![](https://i.imgur.com/7IqchXz.png) 測試 https 是否有效 , 我們請出 node 的 http . https 模組來幫助我們驗證 ```javascript= const https = require('https'); // cert : https://github.com/do-know/Crypt-LE#for-windows const fs = require('fs'); const options = { key: fs.readFileSync('./account.key'), cert: fs.readFileSync('./domain.csr') }; https.createServer(options, function (req, res) { res.end('secure!'); }).listen(443); // Redirect from http port 80 to https const http = require('http'); http.createServer(function (req, res) { console.log('req.headers=', req.headers); res.writeHead(301, {"Location": "https://" + req.headers['host'] + req.url}); res.end(); }).listen(80, () => console.log('http start well')); // if using ssl cert with Crypt-LE you can use cmd below: // le64.exe -email "andrew7810262000@yahoo.com.tw" -key account.key -csr domain.csr -csr-key domain.key -crt domain.crt -domains "ec2-13-229-136-81.ap-southeast-1.compute.amazonaws.com" -generate-missing -live ``` ### 參考資料 - [selfsigned](https://www.npmjs.com/package/selfsigned) - [zerossl](https://app.zerossl.com/certificates/draft) - [Crypt-LE](https://github.com/do-know/Crypt-LE#for-windows) - [Windows 版 Let's Encrypt for Apache](https://snippetinfo.net/mobile/media/1825) - [certbot](https://certbot.eff.org/lets-encrypt/windows-nginx)