HTTPS 实现

HTTPS 实现

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 工作流程

请添加图片描述

  1. Client发起一个HTTPS(https:/demo.linianhui.dev)的请求,根据RFC2818的规定,Client知道需要连接Server的443(默认)端口。
  2. Server把事先配置好的公钥证书(public key certificate)返回给客户端。
  3. Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。
  4. Client使用伪随机数生成器生成加密所使用的会话密钥,然后用证书的公钥加密这个会话密钥,发给Server。
  5. Server使用自己的私钥(private key)解密这个消息,得到会话密钥。至此,Client和Server双方都持有了相同的会话密钥
  6. Server使用会话密钥加密“明文内容A”,发送给Client。
  7. Client使用会话密钥解密响应的密文,得到“明文内容A”。
  8. 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

  1. 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);
}
  1. 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工作流程

测试工程

http-https

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

002237

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值