Golang使用pfx证书进行接口请求

自签pfx证书,带单个域名

  1. 创建自签的根证书

    openssl genrsa -out myCA.key
    openssl req -new -x509 -days 3650 -key myCA.key -out myCA.crt
  2. 创建自签的证书请求

    openssl genrsa -out mykey.key
    openssl req -new -key mykey.key -out mycert.csr
  3. 通过根证书签发下级证书
    3.1 不带x509v3,即不绑定域名或者IP

    openssl x509 -req -CA myCA.crt -CAkey myCA.key -CAcreateserial -days 3560 -in mycert.csr -out mycert.crt

    3.2 带上x509v3就可以绑定域名或IP

    vim v3.ext # 在当前目录下新创建此文件,内容如下

    添加配置内容

    [default]
    # Extensions to add to a certificate request
    #basicConstraints = CA:TRUE
    #keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    subjectAltName=@alt_names
    [alt_names]
    #DNS.1 = flymote.com
    #DNS.2 = *.flymote.com
    #DNS.3 = www.flymot.com
    #DNS.4 = *.flymot.com
    IP.1 = 1.2.3.4
    IP.2 = 5.6.7.8 # 此处就可以配置IP,可以多个,域名也如下所示

    生成证书请求文件

    openssl x509 -req -CA myCA.crt -CAkey myCA.key -CAcreateserial -days 3560 -in mycert.csr -out mycert-domain.crt -extfile v3.ext
  4. 把上面生成的证书转换为 pfx 文件

    openssl pkcs12 –export –in [用户证书文件] –certfile [CA根证书文件] -inkey [用户私钥文件] –passout pass:[P12文件的加密密码] –out [P12文件] -name [用户证书别名] –caname [CA证书别名]
    #可能需要手动输入密码
    openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt -password pass:xxxxxxxx

    导出成pfx的时候,如果加了 -name 或者 -cname 参数,在生成的时候又没有,就可能报错: ANS1 结构错误

  5. 通过以上操作,得到的文件如下图
    签发的证书所有文件

Server端进行SSL的监听

使用Golang启动一个SSL的Server,代码如下

package main

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

// 监听一个HTTPS端口的服务器
func main() {
    // 直接在生成 server 的时候加载 SSL 的配置
    server := &http.Server{
        Addr:         ":8443",
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        TLSConfig:    tlsConfig(),
    }
    # 请求处理
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        msg := fmt.Sprintf("Protocol: %s IP:%s ServerName:%s Scheme:%s Host:%s",
            r.Proto, r.RemoteAddr, r.TLS.ServerName, r.URL.Path, r.Host)
        _, _ = w.Write([]byte(msg))
    })
    // 如果上面不指定SSL配置,可以在此指定两个SSL的文件
    if err := server.ListenAndServeTLS("", ""); err != nil {
        log.Fatal(err)
    }
}

// SSL配置生成
func tlsConfig() *tls.Config {
    # 加载证书
    crt, err := ioutil.ReadFile("../domain_ssl/mycert.crt")
    if err != nil {
        log.Fatal(err)
    }
    # 加载证书请求
    key, err := ioutil.ReadFile("../domain_ssl/mykey.key")
    if err != nil {
        log.Fatal(err)
    }
    # 合并成证书
    cert, err := tls.X509KeyPair(crt, key)
    if err != nil {
        log.Fatal(err)
    }
    // 加载根证书 签发证书时生成的,client 请求时带上同样的,就可以过验证了
    rootCa, err := ioutil.ReadFile("../domain_ssl/myCA.crt")
    if err != nil {
        log.Fatal(err)
    }
    caCertPool := x509.NewCertPool()
    ok := caCertPool.AppendCertsFromPEM(rootCa)
    if !ok {
        panic("failed to parse root certificate")
    }
    # 生成TLS配置
    return &tls.Config{
        Certificates: []tls.Certificate{cert},
        ServerName:   "localhost", // 这个为生成证书是指定的域名或IP地址
        RootCAs:      caCertPool,
    }
}

Client端 pfx 证书请求接口

客户端需要读取pfx文件来请求接口,也可以直接读取 crt 或者 key 证书,如下代码为读取pfx文件

package main

import (
    "crypto/rsa"
    "crypto/tls"
    "crypto/x509"
    "encoding/pem"
    "errors"
    "github.com/astaxie/beego/logs"
    "golang.org/x/crypto/pkcs12"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

// 从 pfx 证书文件中进行SSL请求
func main() {
    dir, _ := os.Getwd()
    pfxFile := dir + "./domain_ssl/mycert-domain.pfx"
    pfxPwd := "pass:123456" // 证书签发时的密码
    err := sslClientFromPfx(pfxFile, pfxPwd)
    if err != nil {
        logs.Error("出错了:%v", err)
        return
    }
}
// pfx 的SSL请求
func sslClientFromPfx(pfxFile, pfxPwd string) (err error) {
    f, err := os.Open(pfxFile)
    if err != nil {
        return err
    }
    // 读取pfx文件内容
    bytes, err := ioutil.ReadAll(f)
    if err != nil {
        return err
    }
    // 因为pfx证书公钥和密钥是成对的,所以要先转成 pem.Block
    blocks, err := pkcs12.ToPEM(bytes, pfxPwd)
    if err != nil {
        return err
    }
    if len(blocks) != 2 {
        return errors.New("密钥长度错误")
    }
    var pemData []byte
    for _, b := range blocks {
        pemData = append(pemData, pem.EncodeToMemory(b)...)
    }
    // then use PEM data for tls to construct tls certificate:
    cert, err := tls.X509KeyPair(pemData, pemData)
    if err != nil {
        return err
    }
    // 根证书 要使用签发时候生成的
    rootCa, err := ioutil.ReadFile("./domain_ssl/myCA.crt")
    if err != nil {
        return err
    }
    caCertPool := x509.NewCertPool()
    ok := caCertPool.AppendCertsFromPEM(rootCa)
    if !ok {
        panic("failed to parse root certificate")
    }
    // Setup HTTPS client
    tlsCfg := &tls.Config{
        Certificates: []tls.Certificate{cert},
        // 域名,签发证书的时候绑定的,如果改为:localhost22 就不一致了,
        // 报错: x509: certificate is valid for localhost, not localhost22
        // 如果改为: 192.168.100.100 ,就会报错: 
        // x509: cannot validate certificate for 192.168.100.100 because it doesn't contain any IP SANs
        // 因为在签发证书的时候,没有配置IP
        ServerName:         "localhost", // 此处理的域名,可以和请求的URL地址中的域名不一致!
        RootCAs:            caCertPool,
        InsecureSkipVerify: false, // 跳过验证,默认为 false
    }
    httpTs := &http.Transport{
        TLSClientConfig:    tlsCfg,
        DisableCompression: true,
    }
    client := &http.Client{Transport: httpTs}
    url := "http://192.168.100.100:8443"
    arg := `{"phone":"15712033162","value":"n/Gp3q190XwdIcBQIwaGYmYUIJR1QakCreUYwr09KmQ=","type":"pass","channel":65535}`
    res, err := client.Post(url, "Content-Type:application/json", strings.NewReader(arg))
    if err != nil {
        return err
    }
    defer res.Body.Close()
    all, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return
    }
    logs.Info("请求响应:%s", all)
    return
}

问题总结

  • x509: certificate signed by unknown authority

    使用的是自签的CA证书,在服务启动和客户机请求的时候最好带上,如下图
    带上自签的根证书-Server端与Client端同样

参考

证书crt、pem、pfx、cer、key 作用及区别
Openssl用公钥crt+私钥key生成pfx
openssl 自签证书(带ip或者域名)
基于OpenSSL的CA建立及证书签发(签发单域名/IP)
使用 Go 实现 TLS socket server
golang使用ssl/tls加密tcp(不是https)
golang解析pfx证书文件获得证书私钥
使用 golang 如何生成 rsa 证书,然后将私钥导出到 pfx