HTTPS 介绍
HTTPS 简史
在早期HTTP诞生的这几年间,1990年~·1994年,HTTP作为一个应用层协议,它是这样工作的:
后来网景公司开发了SSL(Secure Sockets Layer)技术,然后它就变成了这样的HTTP,也就是HTTPS了:
后来爆发了与IE的世纪大战,网景败北,SSL移交给了IETF(Internat Engineering Task Force)互联网工程任务组,标准化之后变成了现在的TLS,现在一般会把它们两个放在一起称为SSL/TLS。本篇并不关注SSL/TLS具体是如何工作的,只是抽象的解释下HTTPS的一个工作流程。
HTTPS 工作流程
- Client发起一个HTTPS(https:/demo.linianhui.dev)的请求,根据RFC2818的规定,Client知道需要连接Server的443(默认)端口。
- Server把事先配置好的公钥证书(public key certificate)返回给客户端。
- Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。
- Client使用伪随机数生成器生成加密所使用的会话密钥,然后用证书的公钥加密这个会话密钥,发给Server。
- Server使用自己的私钥(private key)解密这个消息,得到会话密钥。至此,Client和Server双方都持有了相同的会话密钥。
- Server使用会话密钥加密“明文内容A”,发送给Client。
- Client使用会话密钥解密响应的密文,得到“明文内容A”。
- Client再次发起HTTPS的请求,使用会话密钥加密请求的“明文内容B”,然后Server使用会话密钥解密密文,得到“明文内容B”。
搭建https服务
首先确认安装OpenSSL
确定OpenSSL版本:
$openssl version
如果版本低于1.0.1f,建议升级,因为1.0.1f版本之下的OpenSSL有一个Heartbleed漏洞。
安装OpenSSL:
$sudo apt-get install openssl
自建CA
因为向CA申请签名是需要收费的,所以我们选择自己搭建一个CA来完成这个实验过程。
首先建立myCA目录用于存放CA相关信息
cd && mkdir -p myCA/signedcerts && mkdir myCA/private && cd myCA
myCA 用于存放 CA 根证书,证书数据库,以及后续服务器生成的证书,密钥以及请求
signedcerts:保存签名证书的 copy
private: 包含私钥
之后配置myCA相关参数,在myCA目录下进行
echo '01'>serial && touch index.txt
然后创建 caconfig.cnf 文件
vim ~/myCA/caconfig.cnf
caconfig.cnf文件内容如下
# My sample caconfig.cnf file.<div align='center' ><font size='70'>HTTPS 实现</font></div>
#
# Default configuration to use when one is not provided on the command line.
#
[ ca ]
default_ca = local_ca
#
#
# Default location of directories and files needed to generate certificates.
#
[ local_ca ]
dir = /home/didi/myCA # 这里要将username替换为你的用户名
certificate = $dir/cacert.pem
database = $dir/index.txt
new_certs_dir = $dir/signedcerts
private_key = $dir/private/cakey.pem
serial = $dir/serial
#
#
# Default expiration and encryption policies for certificates.
#
default_crl_days = 365
default_days = 1825
default_md = SHA256
#
policy = local_ca_policy
x509_extensions = local_ca_extensions
#
#
# Default policy to use when generating server certificates. The following
# fields must be defined in the server certificate.
#
[ local_ca_policy ]
commonName = supplied
stateOrProvinceName = supplied
countryName = supplied
emailAddress = supplied
organizationName = supplied
organizationalUnitName = supplied
#
#
# x509 extensions to use when generating server certificates.
#
[ local_ca_extensions ]
subjectAltName = DNS:localhost
basicConstraints = CA:false
nsCertType = server
#
#
# The default root certificate generation policy.
#
[ req ]
default_bits = 2048
default_keyfile = /home/didi/myCA/private/cakey.pem # 这里要将username替换为你的用户名
default_md = SHA256
#
prompt = no
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions
#
#
# Root Certificate Authority distinguished name. Change these fields to match
# your local environment!
#
[ root_ca_distinguished_name ]
commonName = MyOwn Root Certificate Authority # CA机构名
stateOrProvinceName = BJ # CA所在省份
countryName = CN # CA所在国家(仅限2个字符)
emailAddress = XXXX@XXX.com # 邮箱
organizationName = XXX #
organizationalUnitName = XXX #
#
[ root_ca_extensions ]
basicConstraints = CA:true
生成 CA 根证书和密钥
export OPENSSL_CONF=~/myCA/caconfig.cnf #该命令用于给环境变量 OPENSSL_CONF 赋值为caconfig.cnf。
openssl req -x509 -newkey rsa:2048 -out cacert.pem -outform PEM -days 1825 # 生成 CA 根证书和密钥
该命令需要用户设置密码。不要忘记。
以上步骤生成了 CA 自签名根证书,和 RSA 公/私密钥对。证书的格式是 PEM,有效期是1825天。
- /myCA/cacert.pem: CA 根证书
- /myCA/private/cakey.pem: CA 私钥
创建服务器公私钥
生成服务器配置文件exampleserver.cnf
vim ~/myCA/exampleserver.cnf
exampleserver.cnf文件内容如下
#
# exampleserver.cnf
#
[ req ]
prompt = no
distinguished_name = server_distinguished_name
[ server_distinguished_name ]
commonName = localhost # 服务器域名
stateOrProvinceName = BJ # 服务器所在省份
countryName = CN # 服务器所在国家(仅限2个字符)
emailAddress = XXXX@XXX.com # 邮箱
organizationName = XXX #
organizationalUnitName = XXX #
生成服务器证书和密钥
export OPENSSL_CONF=~/myCA/exampleserver.cnf # 该命令设置环境变量 OPENSSL_CONF,使得 openssl 更换配置文件。
openssl req -newkey rsa:1024 -keyout tempkey.pem -keyform PEM -out tempreq.pem -outform PEM
同样的,需要输入密码短语。
之后,有2种对临时秘钥的操作,选择其一即可
1.将临时私钥转换为 unencrypted key,即秘钥不加密状态。
openssl rsa -in tempkey.pem -out server_key.pem
需要输入密码短语。
2.如果希望将 key 保持为加密状态,直接改名
mv tempkey.pem server_key.pem
两者的区别是,第二种需要在服务器启动时输入私钥的密码短语,否则会导致服务器启动失败,但第二种安全性高于第一种,可以更好的保护秘钥。
使用 CA key 对服务器证书签名
export OPENSSL_CONF=~/myCA/caconfig.cnf
openssl ca -in tempkey.pem -out server_crt.pem
这里提示:
Can't open /home/shawn/myCA/index.txt.attr for reading, No such file or directory
140344032040704:error:02001002:system library:fopen:No such file or directory:crypto/bio/bss_file.c:74:fopen('/home/shawn/myCA/index.txt.attr','r')
140344032040704:error:2006D080:BIO routines:BIO_new_file:no such file:crypto/bio/bss_file.c:81:
是Openssl的bug,在myCA目录下再touch一个index.txt.attr即可
删除临时证书和密码文件
rm -f tempkey.pem && rm -f tempreq.pem
现在,自签名的服务器证书和密钥对便产生了:
- server_crt.pem : 服务器证书文件
- server_key.pem : 服务器密钥文件
搭建https工程
基于 cpp-httplib搭建工程 , 工程地址 :http-https
- server 端代码
#include <httplib.h>
#include <fstream>
#include <iostream>
#include <string>
using namespace std::placeholders;
class TEST
{
private:
httplib::Server *m_svr;
void recvGetHelloHandle(const httplib::Request &req, httplib::Response &res);
public:
TEST();
~TEST()
{
delete m_svr;
}
void start(int port)
{
m_svr->listen("0.0.0.0", port);
}
};
TEST::TEST()
{
m_svr = new httplib::Server;
httplib::Server::Handler getHello_cb = std::bind(&TEST::recvGetHelloHandle, this, _1, _2);
m_svr->Get("/hello", getHello_cb);
}
void TEST::recvGetHelloHandle(const httplib::Request &req, httplib::Response &res)
{
if (!req.has_header("token"))
{
res.set_content("fail", "text/plain");
return;
}
std::string token = req.get_header_value("token");
std::cout << "token : " << token << std::endl;
}
class TESTHTTPS
{
private:
httplib::SSLServer *m_svr;
void recvGetHelloHandle(const httplib::Request &req, httplib::Response &res);
public:
TESTHTTPS();
~TESTHTTPS()
{
delete m_svr;
}
void start(int port)
{
m_svr->listen("localhost", port);
}
};
TESTHTTPS::TESTHTTPS()
{
m_svr = new httplib::SSLServer("./data/server_crt.pem", "./data/server_key.pem");
httplib::SSLServer::Handler getHello_cb = std::bind(&TESTHTTPS::recvGetHelloHandle, this, _1, _2);
m_svr->Get("/dd/hello", getHello_cb);
}
void TESTHTTPS::recvGetHelloHandle(const httplib::Request &req, httplib::Response &res)
{
if (!req.has_header("token"))
{
res.set_content("fail", "text/plain");
return;
}
std::string token = req.get_header_value("token");
std::cout << "token : " << token << std::endl;
}
int main(void)
{
TESTHTTPS test;
test.start(8887);
}
- client 端代码
#include <httplib.h>
#include <fstream>
#include <iostream>
#include <string>
class TESTHTTP
{
private:
httplib::Client *m_cli;
public:
TESTHTTP()
{
m_cli = new httplib::Client("https://localhost:8887");
}
~TESTHTTP()
{
delete m_cli;
}
c++
void testGetHello();
};
void TESTHTTP::testGetHello()
{
httplib::Params params = {{"params", "test"}};
httplib::Headers headers = {{"version", "0.0.0.1"},
{"token", "V66n56XJBD/4LTMsGDqE0dlRPtkVmQ0hlIQNwcBMZkU4bQeDTBlMEUluqnha1g/ZsEMK/0JHKtEBwHH2Ko9iEA=="}};
auto res = m_cli->Get("/dd/hello", params, headers);
std::cout << httplib::to_string(res.error()) << std::endl;
std::cout << res->body << std::endl;
}
class TESTHTTPS
{
private:
httplib::Client *m_cli;
public:
TESTHTTPS()
{
m_cli = new httplib::Client("https://localhost:8887");
m_cli->set_ca_cert_path("./data/cacert.pem");
m_cli->enable_server_certificate_verification(true);
}
~TESTHTTPS()
{
delete m_cli;
}
void testGetHello();
};
void TESTHTTPS::testGetHello()
{
httplib::Params params = {{"params", "test"}};
httplib::Headers headers = {{"version", "0.0.0.1"},
{"token", "V66n56XJBD/4LTMsGDqE0dlRPtkVmQ0hlIQNwcBMZkU4bQeDTBlMEUluqnha1g/ZsEMK/0JHKtEBwHH2Ko9iEA=="}};
auto res = m_cli->Get("/dd/hello", params, headers);
std::cout << httplib::to_string(res.error()) << std::endl;
std::cout << res->body << std::endl;
}
int main(void)
{
// TESTHTTP T;
// T.testPostPush();
TESTHTTPS T;
T.testGetHello();
}
参考
信安实践——自建CA证书搭建https服务器
HTTPS工作流程