赞
踩
最近在学习安全协议,大多数实验都是基于Windows下IIS,或者Linux下nginx搭建的Web服务,搭建环境和编写配置文件比较麻烦。而且我有多个不同环境的设备,折腾起来也比较麻烦,最好是开箱即用。
所以我就用nodejs + express写了个跨平台的项目专门用于对比没有使用SSL/TLS的http与使用了的https协议。
如果你对代码不感兴趣,仅是想要分析协议,可以到文章末尾的Github仓库中去下载该项目运行即可。
安装nodejs,访问官网https://nodejs.org,建议下载LTS版本并安装
node --version
npm --version
运行上方命令出现版本号即可。
在终端中运行如下命令安装yarn
npm -g install yarn
yarn --version
出现版本号即为成功。
另外需要搭建OpenSSL环境,这个教程网上有很多,请自行搜索。
接下来主要分享代码编写思路。
创建项目目录为http_ssl
,并在终端中进入
mkdir http_ssl
cd http_ssl
使用yarn初始化,按照提示填写或者一路回车即可
yarn init
创建静态资源目录、配置文件目录、源码目录
mkdir asset
mkdir config
mkdir src
添加express包等
yarn add express
在package.json
文件中修改type
为module
,将语法为ES6,并编写启动命令scripts
。(如果没有直接添加)
"type":"module",
"scripts":{
"start":"node ./src/index.js"
}
完整文件大致如下
{
"name": "http_ssl",
"version": "1.0.0",
"main": "index.js",
"author": "cairbin",
"license": "MIT",
"dependencies": {
"express": "^4.19.2"
},
"type":"module",
"scripts":{
"start":"node ./src/index.js"
}
}
我们希望http和https的端口号、主机地址之类的都能够放在配置文件里,而非代码中,这样方便我们更改,于是编写配置文件。
touch config/config.js
我们这里为了让配置文件能够像模块一样倒入,直接为js
后缀,编辑该文件
// config/config.js
export default {
server:{
static: '../asset', //静态资源目录,注意'../',因为index.js在src下,否则找不到这个目录
http:{
host: "localhost", //主机地址
port: 8848 //端口号
},
https:{
host:'localhost',
port:8849
}
}
}
创建src/index.js
为程序入口,我们编写的启动命令就是运行这个文件的
touch src/index.js
然后编写一个404.html
的静态文件
mkdir asset/html/
touch asset/html/404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Page</title>
</head>
<body>
<h1>404 NOT FOUND</h1>
</body>
</html>
项目还需要日志功能,我们没必要搞太复杂,想要功能强大的可以引入winston
包,我这里就简单封装下js自带的console.*
mkdir src/utils/
touch src/utils/logger.js
// src/utils/logger.js const log = (options)=>{ console[options.type](`[${options.type.toUpperCase()}] ${options.msg}`) } const info = (msg)=>{ log({ type:"info", msg:msg }); } const warn = (msg)=>{ log({ type:'warn', msg:msg }); } const error = (msg)=>{ log({ type:'error', msg:msg }); } export default{ info,warn,error }
尽管是个Demo,但最好还是遵循MVC,我们创建目录和文件,编写一个简单控制器
mkdir src/controller/
touch src/controller/defaultController.js
// src/controller/defaultController.js
import logger from './../utils/logger.js'; //日志模块
const defaultFunc = (req, res, next)=>{
logger.info('exec defultController.defaultFunc');
res.status(500).send('Hello World!'); //为了抓包分析方便,返回500状态码而非200
}
export default{
defaultFunc
}
这里注意,为了抓包方便观察,我们返回的Code为500,而不是200。
创建Express的路由
mkdir src/route/
touch src/route/defaultRoute.js
// src/route/defaultRoute.js
import express from "express";
import defaultController from './../controller/defaultController.js';
import logger from './../utils/logger.js'; //日志模块
const router = express.Router();
router.get('/',(req,res,next)=>{
logger.info('defaultRoute, path=/');
defaultController.defaultFunc(req, res, next);
})
export default router;
创建http和https服务,指定静态资源目录,设置404页面,并集成使用以上编写的文件
// src/index.js import express from 'express'; import https from 'https'; import http from 'http'; import defaultRoute from './route/defaultRoute.js' import fs, { copyFile } from 'fs'; import path from 'path'; import { dirname } from "node:path"; import { fileURLToPath } from "node:url" import config from './../config/config.js'; import logger from './utils/logger.js'; const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const app = express(); const httpsServer = https.createServer({ key:fs.readFileSync('keys/private.pem'), cert:fs.readFileSync('keys/file.crt') },app) const httpServer = http.createServer({},app); //static path app.use(express.static( path.join(__dirname, config.server.static))); logger.info(`static file path '${path.join(__dirname, config.server.static)}'`); app.use('/',defaultRoute); // 404 page app.use('*',(req,res)=>{ res.redirect('./html/404.html') }) httpsServer.listen(config.server.https.port, config.server.https.host, ()=>{ logger.info(`https service is running on https://${config.server.https.host}:${config.server.https.port}`); }) httpServer.listen(config.server.http.port, config.server.http.host, ()=>{ logger.info(`http service is running on http://${config.server.http.host}:${config.server.http.port}`); })
你会发现keys
这个用于存放证书的目录以及证书文件并不存在,我们接下来创建它们项目才能运行。
另外需注意,上面的静态资源目录用path.join()
来设为绝对路径。
我们使用OpenSSL自签一个证书,在macOS或者Linux下可编写脚本
touch generate.sh
# generate.sh
mkdir -p keys
openssl genrsa 4096 > keys/private.pem
openssl req -new -key keys/private.pem -out keys/csr.pem
openssl x509 -req -days 365 -in keys/csr.pem -signkey keys/private.pem -out keys/file.crt
然后运行脚本
sh generate.sh
如果是Windows的话也可以编写.bat
批处理文件,或者直接执行命令:
mkdir keys
openssl genrsa 4096 > keys/private.pem
openssl req -new -key keys/private.pem -out keys/csr.pem
openssl x509 -req -days 365 -in keys/csr.pem -signkey keys/private.pem -out keys/file.crt
注意旧版本的CMD或PowerShell可能不支持/
,可以改用\
,可能需要转义字符\\
执行我们编写好的指令(package.json
那里的)运行项目:
yarn run start
正常情况下,控制台应该是在报[INFO]
浏览器地址栏输入http://localhost:8848
,即可看到Hello World!
页面(最指定协议头,有些浏览器会自动填充为https)
https的话请访问https://localhost:8849
,正常来讲现代的浏览器会阻止你访问这个页面,并提示非私人连接不安全,因为我们用的是自签名的证书。
直接无视风险,继续访问(doge)
关闭页面,在浏览器的设置里,清除浏览器缓存(这一步很重要,因为我们之前访问过页面了,浏览器为了加速一般都会有缓存导致下次访问时不是向服务器请求,而是加载本地缓存的页面,导致抓包失败)
启动wireshark并进行抓包,与其他情况不同,由于是回环地址,所以我们要抓loopback的包
先来访问http未加密的页面,在wireshark中使用过滤器过滤http的包,该项目为了排除抓包的时候的干扰,访问页面返回的状态码为500,而不是200,所以我们只需要找到http status 为500的包即可
查看数据,果然是明文
再以https协议的方式访问一次并进行抓包
发现抓不到了,我们将过滤器条件改为tls
由此看到数据都被保护起来了,即使抓到也都是密文。
Wireshark是直接看到TLS封包的内容的,我们可以配置环境变量,让浏览器得到的preMasterKey放到指定log中,wireshark支持读取这个文件,然后再进行抓包就可以查看加密的数据了。
文章可以参考 https://segmentfault.com/a/1190000018746027
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。