赞
踩
本文是在学习https的时候,参照网上诸多资料,在实际操作过程中还是遇到不少问题,力求详细的阐述在linux环境下,c++使用openssl对http报文加密成为https的过程,并使用tcpdump抓取tcp报文展示http和https在传输过程中的差别。
本文适合对c++ openssl一点了解都没有,服务器端编程,只需要把ssl添加上tcp连接上。
参照文章:
C++通过openssl搭建https服务器
OpenSSL生成CA自签名根证书和颁发证书
tcpdump抓取TCP/IP数据包分析
openssl基本原理 + 生成证书 + 使用实例
总述:https相当于在http连接上添加上一层ssl验证。
如图所示,与没有ssl验证相比,在服务器端,listen之前设置ssl证书,在accept之后使用ssl_accept进行ssl的连接,相当于套了一层壳。
http.cpp
/* 接受一个tcp请求,简简单单发送发送一个http响应报文 */ #include <string> #include <sys/socket.h> #include <sys/stat.h> #include <netinet/in.h> #include <sys/types.h> #include <arpa/inet.h> #include <sys/un.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> using namespace std; int main(int argc, char* argv[]){ if(argc < 3){ printf("need filename ip-address port\n"); return 1; } //ip地址 char *ip = argv[1]; //端口 int port = atoi(argv[2]); //创建socket,ipv4.tcp int listenfd = socket(PF_INET, SOCK_STREAM, 0); if(listenfd == -1){ printf("Create socket error %d", errno); return 1; } //命名socket //创建ipv4地址 struct sockaddr_in m_addr; bzero(&m_addr, sizeof(m_addr)); m_addr.sin_family = AF_INET; inet_pton(AF_INET, ip, &m_addr.sin_addr); m_addr.sin_port = htons(port); //绑定 int ret = bind(listenfd, (struct sockaddr*)&m_addr, sizeof(m_addr)); if(ret == -1){ printf("Socket bind error %d", ret); return 1; } //监听 ret = listen(listenfd, 100); if(ret == -1){ printf("Listen error %d", ret); return 1; } while(1){ struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); int new_con = accept(listenfd, (sockaddr *)&addr, &addrlen); if(new_con == -1){ printf("accept error, errno = %d",errno); continue; } else { printf("accept %d success\n", new_con); } string html_file = "welcome.html"; int fd = open(html_file.c_str(), O_RDONLY); struct stat file_stat; stat(html_file.c_str(), &file_stat); void *html_ = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); string buf_w = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n" "Content-Length: " + to_string(file_stat.st_size) + "\r\n" "\r\n"; buf_w += (char *)html_; printf("send %d bytes\n", send(new_con, (void*)buf_w.c_str(), buf_w.size(), 0)); munmap(html_, file_stat.st_size); sleep(2); close(new_con); } return 0; }
其中welcome.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebServer</title> </head> <body> <br/> <br/> <div align="center"><font size="5"> <strong>hello http</strong></font></div> <br/> <br/> <form action="5" method="post"> <div align="center"><button type="submit">点赞</button></div> </form> <br/> <form action="6" method="post"> <div align="center"><button type="submit" >收藏</button></div> </form> <br/> <form action="7" method="post"> <div align="center"><button type="submit">关注</button></div> </form> </div> </body> </html>
注意,html文件要和http.cpp文件在同一文件夹下才能被准确打开。
文件结构:
<pre>.
├── http
├── http.cpp
└── welcome.html
</pre>
编译完成后,在http所在的位置命令行键入$ ./http 127.0.0.1 1234
开始等待连接。
同时另开一终端,输入$sudo tcpdump -A -i lo port 1234
开始监听本地回环lo的1234端口的信息。
打开postman发送get报文
或者打开浏览器,输入127.0.0.1:1234/
此时tcpdump抓取到的信息,可以看到是明文传输的,这也是问什么需要https的原因。
[zsz@localhost https]$ sudo tcpdump -A -i lo port 1234 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 22:11:03.930244 IP localhost.50744 > localhost.search-agent: Flags [S], seq 741845069, win 43690, options [mss 65495,sackOK,TS val 1659589 ecr 0,nop,wscale 7], length 0 E..<..@.@.B1.........8..,7.M.........0......... ..R......... 22:11:03.930333 IP localhost.search-agent > localhost.50744: Flags [S.], seq 2115968623, ack 741845070, win 43690, options [mss 65495,sackOK,TS val 1659590 ecr 1659589,nop,wscale 7], length 0 E..<..@.@.<............8~..o,7.N.....0......... ..R...R..... 22:11:03.930368 IP localhost.50744 > localhost.search-agent: Flags [.], ack 1, win 342, options [nop,nop,TS val 1659590 ecr 1659590], length 0 E..4..@.@.B8.........8..,7.N~..p...V.(..... ..R...R. 22:11:03.930742 IP localhost.search-agent > localhost.50744: Flags [P.], seq 1:841, ack 1, win 342, options [nop,nop,TS val 1659590 ecr 1659590], length 840 E..|L.@.@..............8~..p,7.N...V.q..... ..R...R.HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Connection: close Date: Fri, 23 Nov 2018 02:01:05 GMT Content-Length: 704 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>WebServer</title> </head> <body> <br/> <br/> <div align="center"><font size="5"> <strong>hello http</strong></font></div> <br/> <br/> <form action="5" method="post"> <div align="center"><button type="submit">......</button></div> </form> <br/> <form action="6" method="post"> <div align="center"><button type="submit" >......</button></div> </form> <br/> <form action="7" method="post"> <div align="center"><button type="submit">......</button></div> </form> </div> </body> </html> 22:11:03.930768 IP localhost.50744 > localhost.search-agent: Flags [.], ack 841, win 355, options [nop,nop,TS val 1659590 ecr 1659590], length 0 E..4..@.@.B7.........8..,7.N~.!....c.(..... ..R...R. 22:11:03.931909 IP localhost.50744 > localhost.search-agent: Flags [P.], seq 1:202, ack 841, win 355, options [nop,nop,TS val 1659592 ecr 1659590], length 201 E.....@.@.Am.........8..,7.N~.!....c....... ..R...R.GET / HTTP/1.1 User-Agent: PostmanRuntime/7.29.2 Accept: */* Postman-Token: 9d205642-3df0-49c7-87a5-74e73f2d731d Host: 127.0.0.1:1234 Accept-Encoding: gzip, deflate, br Connection: keep-alive
https需要证书,可以使用openssl进行自签发证书。
详细可以自行搜索
下边三行分别是生成私钥,证书申请文件,生成ca证书.
注意生成私钥是要键入密码的,证书申请和生成要填很多信息,看说明填入。
openssl genrsa -des3 -out privkey.pem 2048
openssl req -new -key privkey.pem -out cert.csr
openssl x509 -req -in cert.csr -out cert.pem -signkey privkey.pem -days 3650
最后会生成privkey.pem和cert.pem,分别用来ssl验证私钥和证书。
和http.cpp的区别就是一开始多了初始化,在accept之后用SSL_accept再次接受一次。
https.cpp
使用g++编译的时候要加上-lssl -lcrypto
两个库
/* 接受一个tcp请求,简简单单发送发送一个http响应报文 */ #include <string> #include <sys/socket.h> #include <sys/stat.h> #include <netinet/in.h> #include <sys/types.h> #include <arpa/inet.h> #include <sys/un.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <openssl/ssl.h> #include <openssl/err.h> #include "assert.h" using namespace std; SSL_CTX *ctx = NULL; bool InitSSL(const char* cacert, const char* key, const char* passwd){ // 初始化 SSLeay_add_ssl_algorithms(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); ERR_load_BIO_strings(); // 我们使用SSL V3,V2 assert((ctx = SSL_CTX_new(SSLv23_method())) != NULL); // 要求校验对方证书,这里建议使用SSL_VERIFY_FAIL_IF_NO_PEER_CERT,详见https://blog.csdn.net/u013919153/article/details/78616737 //对于服务器端来说如果使用的是SSL_VERIFY_PEER且服务器端没有考虑对方没交证书的情况,会出现只能访问一次,第二次访问就失败的情况。 SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); // 加载CA的证书 assert(SSL_CTX_load_verify_locations(ctx, cacert, NULL)); // 加载自己的证书 assert(SSL_CTX_use_certificate_chain_file(ctx, cacert) > 0); //assert(SSL_CTX_use_certificate_file(ctx, "cacert.pem", SSL_FILETYPE_PEM) > 0); // 加载自己的私钥 SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)passwd); assert(SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) > 0); // 判定私钥是否正确 assert(SSL_CTX_check_private_key(ctx)); return true; } int main(int argc, char* argv[]){ string cacert = "cert.pem"; string key = "privkey.pem"; string passwd = "123456"; if(!InitSSL(cacert.c_str(), key.c_str(), passwd.c_str())){ printf("init ssl error\n"); return 0; } if(argc < 3){ printf("need filename ip-address port\n"); return 1; } //ip地址 char *ip = argv[1]; //端口 int port = atoi(argv[2]); //创建socket,ipv4.tcp int listenfd = socket(PF_INET, SOCK_STREAM, 0); if(listenfd == -1){ printf("Create socket error %d", errno); return 1; } //命名socket //创建ipv4地址 struct sockaddr_in m_addr; bzero(&m_addr, sizeof(m_addr)); m_addr.sin_family = AF_INET; inet_pton(AF_INET, ip, &m_addr.sin_addr); m_addr.sin_port = htons(port); //绑定 int ret = bind(listenfd, (struct sockaddr*)&m_addr, sizeof(m_addr)); if(ret == -1){ printf("Socket bind error %d", ret); return 1; } //监听 ret = listen(listenfd, 100); if(ret == -1){ printf("Listen error %d", ret); return 1; } while(1){ struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); int new_con = accept(listenfd, (sockaddr *)&addr, &addrlen); if(new_con == -1){ printf("accept error, errno = %d",errno); continue; } else { printf("accept %d success\n", new_con); } //ssl SSL *ssl = SSL_new(ctx); if(ssl == NULL) { printf("ssl new wrong\n"); return 0; } SSL_set_accept_state(ssl); //关联sockfd和ssl SSL_set_fd(ssl, new_con); int ret = SSL_accept(ssl); if(ret != 1){ printf("%s\n", SSL_state_string_long(ssl)); printf("ret = %d, ssl get error %d\n", ret, SSL_get_error(ssl, ret)); } // string html_file = "welcome.html"; int fd = open(html_file.c_str(), O_RDONLY); struct stat file_stat; stat(html_file.c_str(), &file_stat); void *html_ = mmap(nullptr, file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); string buf_w = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n" "Content-Length: " + to_string(file_stat.st_size) + "\r\n" "\r\n"; buf_w += (char *)html_; //把send换成SSL_write //printf("send %d bytes\n", send(new_con, (void*)buf_w.c_str(), buf_w.size(), 0)); printf("send %d bytes\n", SSL_write(ssl, (void*)buf_w.c_str(), buf_w.size())); munmap(html_, file_stat.st_size); //关闭 SSL_shutdown(ssl); SSL_free(ssl); close(new_con); } SSL_CTX_free(ctx); return 0; }
如果使用postman,要把证书验证关掉,因为这个证书是我们自己发布的,不是官方,
在ip前边加上https前缀
如果使用浏览器,会提示不安全连接,直接同意就行了,要注意https前缀
和前边一样使用tcpdump抓包,可以看到都是乱码
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes 23:00:32.693162 IP localhost.51284 > localhost.search-agent: Flags [S], seq 956600350, win 43690, options [mss 65495,sackOK,TS val 4628353 ecr 0,nop,wscale 7], length 0 E..<..@.@.-..........T..9............0......... .F.......... 23:00:32.693283 IP localhost.search-agent > localhost.51284: Flags [S.], seq 2464157102, ack 956600351, win 43690, options [mss 65495,sackOK,TS val 4628353 ecr 4628353,nop,wscale 7], length 0 E..<..@.@.<............T....9........0......... .F...F...... 23:00:32.693385 IP localhost.51284 > localhost.search-agent: Flags [.], ack 1, win 342, options [nop,nop,TS val 4628353 ecr 4628353], length 0 E..4..@.@.-".........T..9..........V.(..... .F...F.. 23:00:32.700124 IP localhost.51284 > localhost.search-agent: Flags [P.], seq 1:518, ack 1, win 342, options [nop,nop,TS val 4628360 ecr 4628353], length 517 E..9..@.@.+..........T..9..........V....... .F...F.............G5.E...YS.h...T........$.Dd.r.;. .......Sid.,] L..#EH.X...... .&'.$.......,. .+... .0.../.......5.../. .............. .......................#........#.lJ'.y..[....... .N..!../.........!..U......\ .z..y.L[...F.0......B."....aB.N.Lt............QA.O..JJ@.4%.....I}/.f..9...c..{.'j...... . .j..^..s...+..;.p.......h2.http/1.1..........3.k.i... ...MtF>rzx.C.zU..+.8.fv..>mgb.zA...A.D.I....l6......)!E...tX[...D;i.s#.q.s..W...3..y.....0.4....IN$>Z.+....................................-........@................ 23:00:32.700227 IP localhost.search-agent > localhost.51284: Flags [.], ack 518, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0 E..4..@.@..............T....9..$...^.(..... .F...F.. 23:00:32.700410 IP localhost.search-agent > localhost.51284: Flags [F.], seq 1, ack 518, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0 E..4./@.@..............T....9..$...^.(..... .F...F.. 23:00:32.700799 IP localhost.51284 > localhost.search-agent: Flags [F.], seq 518, ack 2, win 342, options [nop,nop,TS val 4628360 ecr 4628360], length 0 E..4..@.@.- .........T..9..$.......V.(..... .F...F.. 23:00:32.700815 IP localhost.search-agent > localhost.51284: Flags [.], ack 519, win 350, options [nop,nop,TS val 4628360 ecr 4628360], length 0 E..4.0@.@..............T....9..%...^.(..... .F...F..
在学习如何使用https的时候,一开始概念很清楚,很多文章也直接明了的说了是在http和tcp中间加上一层ssl验证,但是对于证书如何获取,以及在代码中如何体现却很难找到一篇完整论述的文章,大部分文章都是基于读者有一定基础来写,加上c++实现的https服务器基本是体量很大,很难在庞大的代码中找到自己想要的,也就是关于ssl如何建立的部分。
因此本文的主要目的不是说让读者建立一个完整的https的概念,或者掌握大部分openssl的api,而是说给出一个可以跑的通的代码,一个非常简洁的代码,一套跟着做就能实现的使用openssl+https的完整流程。
这篇文章也是留个自己的一个教程,之后建立https服务器的时候,也可以根据这篇文章结合其他文章迅速回忆起相关知识,直接建立服务。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。