【实践】如何在本地环境用GO实现HTTPS链接?

December 09, 2023
测试
测试
测试
测试
21 分钟阅读

1,摘要

本篇文章是基于实操,如何在本地环境用GO实现HTTPS链接。原理部分请参考文章《【深度知识】HTTPS协议原理和流程分析》。

2,crt、key以及pem的区别

  • 证书(Certificate) .cer .crt
  • 私钥(Private Key) .key
  • 证书签名请求(Certificate sign request) .csr
  • x509 X.509是一种非常通用的证书格式。所有的证书都符合ITU-T X.509国际标准,因此(理论上)为一种应用创建的证书可以用于任何其他符合X.509标准的应用。 x509证书一般会用到三类文,key,csr,crt。 Key 是私用密钥openssl格,通常是rsa算法。 Csr 是证书请求文件,用于申请证书。在制作csr文件的时,必须使用自己的私钥来签署申,还可以设定一个密钥。 crt是CA认证后的证书文,(windows下面的,其实是crt),签署人用自己的key给你签署的凭证。
  • PEM - Privacy Enhanced Mail,打开看文本格式,以”—–BEGIN…”开头, “—–END…”结尾,内容是BASE64编码. 查看PEM格式证书的信息:openssl x509 -in certificate.pem -text -noout Apache和*NIX服务器偏向于使用这种编码格式.
  • DER - Distinguished Encoding Rules,打开看是二进制格式,不可读. 查看DER格式证书的信息:openssl x509 -in certificate.der -inform der -text -noout 。 Java和Windows服务器偏向于使用这种编码格式.

3,OpenSSL建立HTTPS链接最终失败

按照《TLS完全指南(二):OpenSSL操作指南》文档操作,最终失败。 在浏览器输入"https://localhost/",发现提示为"访问 localhost 的请求遭到拒绝 您未获授权,无法查看此网页。HTTP ERROR 403"。

在linux下使用,提示原因如下。辉哥认为自认证产生的证书不可使用。

[root@iZ23prr3ucfZ server]# curl --cacert cert.pem  https://localhost
curl: (60) Peer certificate cannot be authenticated with known CA certificates
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

放弃该篇文章方法尝试。

4, tls-gen产生私钥建立HTTPS链接

4.1 构建 CA 证书链

tls-gen是一个用 Python 编写的、非常易用的工具。它定义了三种 profile。这里我们选择最简单的一种:一个根证书和一组证书、私钥对。

在linux系统 shell 里面执行一下的命令:

git clone https://github.com/michaelklishin/tls-gen cd tls-gen/basic make CN=localhost

就这样,我们就为域名 localhost创建了一套证书。观察一下当前路径的内容,我们会发现两个新的目录:testca 和 server。前者里面存放了刚刚创建的根证书 (root CA),后者里面存放了我们之后的服务程序要用的的证书和私钥。

testca/ cacert.pem

server/ cert.pem key.pem

这是成功的命令输出结果:
[root@iZ23prr3ucfZ tmp]# git clone https://github.com/michaelklishin/tls-gen
Initialized empty Git repository in /root/tmp/tls-gen/.git/
remote: Enumerating objects: 369, done.
remote: Total 369 (delta 0), reused 0 (delta 0), pack-reused 369
Receiving objects: 100% (369/369), 90.46 KiB | 117 KiB/s, done.
Resolving deltas: 100% (215/215), done.
[root@iZ23prr3ucfZ tmp]# ls
server.crt  server.csr  server.go  server.key  tls-gen
[root@iZ23prr3ucfZ tmp]# cd tls-gen/basic
[root@iZ23prr3ucfZ basic]# make CN=localhost
python3 profile.py regenerate --password "" \
    --common-name localhost \
    --client-alt-name iZ23prr3ucfZ \
    --server-alt-name iZ23prr3ucfZ \
    --days-of-validity 3650 \
    --key-bits 2048 
Removing /root/tmp/tls-gen/basic/testca
Removing /root/tmp/tls-gen/basic/result
Removing /root/tmp/tls-gen/basic/server
Removing /root/tmp/tls-gen/basic/client
Will generate a root CA and two certificate/key pairs (server and client)
=>  [openssl_req]
Generating a 2048 bit RSA private key
..................................+++
.............................................................+++
writing new private key to '/root/tmp/tls-gen/basic/testca/private/cakey.pem'
-----
=>  [openssl_x509]
Will generate leaf certificate and key pair for server
Using localhost for Common Name (CN)
Using parent certificate path at /root/tmp/tls-gen/basic/testca/cacert.pem
Using parent key path at /root/tmp/tls-gen/basic/testca/private/cakey.pem
Will use RSA...
=>  [openssl_genrsa]
Generating RSA private key, 2048 bit long modulus
............................................+++
...............................................................................................+++
e is 65537 (0x10001)
=>  [openssl_req]
=>  [openssl_ca]
Using configuration from /tmp/tmpdaabupsr
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'localhost'
organizationName      :PRINTABLE:'server'
localityName          :T61STRING:'$$$$'
Certificate is to be certified until May  6 06:24:49 2029 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
Will generate leaf certificate and key pair for client
Using localhost for Common Name (CN)
Using parent certificate path at /root/tmp/tls-gen/basic/testca/cacert.pem
Using parent key path at /root/tmp/tls-gen/basic/testca/private/cakey.pem
Will use RSA...
=>  [openssl_genrsa]
Generating RSA private key, 2048 bit long modulus
..............+++
............+++
e is 65537 (0x10001)
=>  [openssl_req]
=>  [openssl_ca]
Using configuration from /tmp/tmpdaabupsr
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :PRINTABLE:'localhost'
organizationName      :PRINTABLE:'client'
localityName          :T61STRING:'$$$$'
Certificate is to be certified until May  6 06:24:49 2029 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
Done! Find generated certificates and private keys under ./result!
python3 profile.py verify
Will verify generated certificates against the CA...
Will verify client certificate against root CA
/root/tmp/tls-gen/basic/result/client_certificate.pem: OK
Will verify server certificate against root CA
/root/tmp/tls-gen/basic/result/server_certificate.pem: OK

4.2 编写服务端程序

Go 对 TLS 的支持还是比较完备。以下是服务器端的代码(server.go):

 package main

 import (
    "log"
    "net/http"
 )

 func HelloServer(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte("This is an example server.\n"))
}

func main() {
    http.HandleFunc("/hello", HelloServer)
    err := http.ListenAndServeTLS(":1443", "server/cert.pem", "server/key.pem", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

可以看到我们创建了一个 HTTP 服务,这个服务监听 1443 端口并且只处理一个路径 /hello。然后调用了下面这个函数来监听 1443 端口。注意我们给出了之前创建的服务的证书和私钥 - 这样就保证了HTTP会用加密的方式来传输。

运行服务程序:

go run server.go

4.3 外部读取文件访问HTTPs服务

编写客户端程序(client.go):

package main

import (
    "net/http"
    "fmt"
    "io/ioutil"
    "strings"
)

func main() {
    client := &http.Client{}
    resp, err := client.Get("https://localhost:1443/hello")
    if err != nil {
        panic("failed to connect: " + err.Error())
    }
    content, _ := ioutil.ReadAll(resp.Body)
    s := strings.TrimSpace(string(content))

    fmt.Println(s)
}

运行 go run client.go,有这样的错误:

panic: failed to connect: Get https://www.mytestdomain.io:1443/hello: x509: certificate signed by unknown authorit

这是因为系统不知道如何来处理这个 self signed 证书。

各个 OS 添加根证书的方法是不同的。对于 Linux 系统 (以 Ubuntu 为例) 来说,把证书文件放到相应的目录即可:

$ sudo cp testca/cacert.pem /etc/ssl/certs

如果是 macOS,可以用一下的命令:

$ sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain testca/cacert.pem

如果是WINDOWS,则在浏览器上把"testca/cacert.pem"导入到“受信任的根证书颁发机构”。

现在我们再次运行刚才那个程就会成功的获得服务端的响应了:

This is an example server.

在浏览器上输入https://localhost:1443/hello可以有正常输出:

4.4 代码读取文件访问HTTPs服务

修改client.go文件为直接读取PEM文件的方式。

package main

import (
    "net/http"
    "fmt"
    "io/ioutil"
    "strings"
    "crypto/x509"
    "crypto/tls"
)

func main() {

    rootPEM, err := ioutil.ReadFile("testca/cacert.pem")
    if err != nil {
        fmt.Print(err)
    }

    roots := x509.NewCertPool()
    ok := roots.AppendCertsFromPEM(rootPEM)
    if !ok {
        panic("failed to parse root certificate")
    }

    tr := &http.Transport{
        TLSClientConfig: &tls.Config{RootCAs: roots},
    }

    client := &http.Client{Transport: tr}
    
    resp, err := client.Get("https://localhost:1443/hello")
    if err != nil {
        panic("failed to connect: " + err.Error())
    }
    content, _ := ioutil.ReadAll(resp.Body)
    s := strings.TrimSpace(string(content))

    fmt.Println(s)
}

也就是说,我们用准备好的 root CA 的内容产生了一个新的 http transport。

运行一下 go run client.go。成功!

This is an example server.

5. 参考

(1)Go代码打通HTTPs https://studygolang.com/articles/12401 (2)TLS完全指南(一):TLS和安全通信 https://zhuanlan.zhihu.com/p/26684050 TLS完全指南(二):OpenSSL操作指南[实际操作失败] https://zhuanlan.zhihu.com/p/26684071 TLS完全指南(三):用Go语言写HTTPS程序 https://zhuanlan.zhihu.com/p/26684081 (3)Go实战--golang中使用HTTPS以及TSL(.crt、.key、.pem区别以及crypto/tls包介绍)[GITHUB] https://blog.csdn.net/wangshubo1989/article/details/77508738

继续阅读

更多来自我们博客的帖子

如何安装 BuddyPress
由 测试 December 17, 2023
经过差不多一年的开发,BuddyPress 这个基于 WordPress Mu 的 SNS 插件正式版终于发布了。BuddyPress...
阅读更多
Filter如何工作
由 测试 December 17, 2023
在 web.xml...
阅读更多
如何理解CGAffineTransform
由 测试 December 17, 2023
CGAffineTransform A structure for holding an affine transformation matrix. ...
阅读更多