动机

公司内网想做一个小型的https,不想申请https证书,想自己做自验证的证书

关注点

  • https的握手流程

    • react_redux
    • 浏览器尝试连接网站 https://demowebsite.com.
    • demowebsite.com server 会把证书送回给浏览器。这个证书包含网站服务器的公钥,还有一些其他信息证明这个公钥属于网站
    • 浏览器验证这个证书来确认他有正确的公钥
    • 浏览器选择一个随机对称key K用来连接到服务器。客户端使用公钥加密K
    • 服务端使用私钥解密K,这样客户端服务器都知道K,但是其他人不知道
    • 接着任何从客户端发送到服务端的信息,都用K加密。
  • 自己做CA

    • 要先创建root CA, 这个root ca不做客户端或者服务器端证书的签发,它是用来创建一个中间CA,这个中间CA会替代root CA签发证书,这样root key就可以离线保存,这样安全性才能得到保证
    • 准备目录结构
      • 创建目录,命令和最后结果类似如下
      mkdir -p /root/ca
       cd /root/ca
       mkdir certs crl newcerts private
       chmod 700 private
       touch index.txt
       touch serial
       echo 1000 > serial
      

      ca root directory

      • 准备配置文件openssl.cnf
      • 创建root key
      cd /root/ca
      openssl genrsa -aes256 -out private/ca.key.pem 4096
      chmod 400 private/ca.key.pem
      
      • 创建root secret
      cd /root/ca
      openssl req -config openssl.cnf \
          -key private/ca.key.pem \
          -new -x509 -days 7300 -sha256 -extensions v3_ca \
          -out certs/ca.cert.pem
      chmod 444 certs/ca.cert.pem
      
      • 验证root证书
          openssl x509 -noout -text -in certs/ca.cert.pem
      
  • 创建中间证书颁发机构

    • 创建目录
    mkdir /root/ca/intermediate
    cd /root/ca/intermediate
    mkdir certs crl csr newcerts private
    chmod 700 private
    touch index.txt
    touch serial
    touch crlnumber
    echo 1000 > serial
    
    • 准备配置文件openssl-intermediate.cnf
    • 创建intermediate key
    cd /root/ca
    openssl req -config intermediate/openssl-intermediate.cnf -new -sha256 \
      -key intermediate/private/intermediate.key.pem \
      -out intermediate/csr/intermediate.csr.pem
    
    • 创建intermediate secret
    cd /root/ca
    openssl ca -config openssl.cnf -extensions v3_intermediate_ca \
      -days 3650 -notext -md sha256 \
      -in intermediate/csr/intermediate.csr.pem \
      -out intermediate/certs/intermediate.cert.pem
    chmod 444 intermediate/certs/intermediate.cert.pem
    
    • 验证证书
    // 和上面一样验证中间证书的有效性
    openssl x509 -noout -text \
      -in intermediate/certs/intermediate.cert.pem
    // 使用根证书验证中间证书
    openssl verify -CAfile certs/ca.cert.pem \
      intermediate/certs/intermediate.cert.pem
    
  • 证书链的生成

    • 一般如果有证书通过中间证书颁发机构来验证,也要去根证书机构验证
    • 使用如下来把根证书和中间证书合并到一起
    cat intermediate/certs/intermediate.cert.pem \
      certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
    chmod 444 intermediate/certs/ca-chain.cert.pem
    
  • 服务器的证书部署

    • 签发服务端和客户端的证书,和上面一样,只不过用中间证书颁发机构的配置来创建
    • 创建key
    cd /root/ca
    openssl genrsa -aes256 \
        -out intermediate/private/www.example.com.key.pem 2048
    chmod 400 intermediate/private/www.example.com.key.pem
    
    • 创建证书签发请求
    cd /root/ca
    openssl req -config intermediate/openssl.cnf \
      -key intermediate/private/www.example.com.key.pem \
      -new -sha256 -out intermediate/csr/www.example.com.csr.pem
    
    • 创建服务端证书
    cd /root/ca
    openssl ca -config intermediate/openssl.cnf \
        -extensions server_cert -days 375 -notext -md sha256 \
        -in intermediate/csr/www.example.com.csr.pem \
        -out intermediate/certs/www.example.com.cert.pem
    chmod 444 intermediate/certs/www.example.com.cert.pem
    
    • 创建完后,可以在intermediate/index.txt文件中有一条相应的记录
    • 验证证书
    openssl x509 -noout -text \
      -in intermediate/certs/www.example.com.cert.pem
    # 使用证书链文件来验证新建的证书
    openssl verify -CAfile intermediate/certs/ca-chain.cert.pem \
      intermediate/certs/www.example.com.cert.pem
    
    • 部署需要的证书
    ca-chain.cert.pem
    www.example.com.key.pem
    www.example.com.cert.pem
    
  • 证书验证

    // 这个命令会显示服务器的CA证书,showcerts
    openssl s_client -connect localhost:443 -prexit -showcerts
    // 验证当前的证书是不是被根ca签发
    openssl verify -verbose -x509_strict -CAfile ca-chain.cert.pem localhost.cert.pem
    
  • 代码里怎么用

    • 建立一个简单的torado web服务器
    • 把上面三个文件拷贝到对应目录
    • 代码如下:
      /* 这里要注意ssl_options,要把证书链也加进去不然是不行的 */
      import tornado.ioloop
      import tornado.web
      import ssl
      class MainHandler(tornado.web.RequestHandler):
          def get(self):
              self.write("Hello, world")
      def make_app():
          return tornado.web.Application([
              (r"/", MainHandler),
          ])
      if __name__ == "__main__":
          application = make_app()
          chainpath="./ca-chain.cert.pem"
          crtpath="./localhost.cert.pem"
          keypath="./localhost.key.pem"
          ssl_ctx= ssl.create_default_context(ssl.Purpose.CLIENT_AUTH,cafile=chainpath)
          ssl_ctx.load_cert_chain(crtpath,keypath)
          http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_ctx)
          http_server.listen(443)
          tornado.ioloop.IOLoop.current().start()
      
  • 如何测试

    • result
    • 浏览器上的话,可以考虑firefox,chrome也可以,不过要自己导入证书,可能还要做格式转化,下面直接在firefox里导入证书,结果如下: result
    • 如果你是用request这类的库,可以像这样做,因为我们对key是做加密的,所以要把密码也传到后端,不能直接request直接用key,cert请求,除非你生成证书的时候不加密码。
    import requests
    from urllib3.util.ssl_ import create_urllib3_context
    from requests.adapters import HTTPAdapter
    import urllib3
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    cert_path = "./localhost.cert.pem"
    private_key_path = "./localhost.key.pem"
    passphrase_key = "xxx"
    class SSLAdapter(HTTPAdapter):
        def init_poolmanager(self, *args, **kwargs):
            context = create_urllib3_context()
            context.load_cert_chain(
                certfile=cert_path, keyfile=private_key_path, password=passphrase_key)
            kwargs['ssl_context'] = context
            return super().init_poolmanager(*args, **kwargs)
    session = requests.Session()
    session.verify = False  # If you don't want to validate server's public certificate
    session.mount("https://", SSLAdapter())
    url = "https://localhost:20191/inference"
    response = session.post(url)
    print(response.json())
    
  • 总结

    • 这里还有些其他主题,比如,证书回收,格式转换方法,测速,最佳实践等,这里主要记录下做这种证书的步骤,其他的值得一提的是,最佳实践,我们一般是做在CI/CD的pipline里边,证书也要做个定时更新,这些都是必不可少的,当然在生成证书之前还有一些对比和选择加密算法的细节,这里就不一一讲了,有兴趣可以自己去找下。