# 如何在 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)