【问题标题】:Go: HTTPS Request using a Client Certificate stored on a SmartCard (Windows)Go:使用存储在 SmartCard 上的客户端证书的 HTTPS 请求 (Windows)
【发布时间】:2020-03-27 15:54:11
【问题描述】:

为了执行客户端证书身份验证(相互身份验证),我发现的所有示例都假设私钥是可访问的(例如,从文件中)。包含私钥和公钥的证书生成如下:

cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")

现在,我必须从智能卡获取证书(和私钥,据我所知无法提取 - 签名应该通过 PKCS#11 完成)。到目前为止,我能够枚举 Windows 证书存储中的证书:

store, err := syscall.UTF16PtrFromString("MY")
storeHandle, err := syscall.CertOpenSystemStore(0, store)
if err != nil {
    fmt.Println(syscall.GetLastError())
}

var certs []*x509.Certificate
var cert *syscall.CertContext
for {
    cert, err = syscall.CertEnumCertificatesInStore(storeHandle, cert)
    if err != nil {
        if errno, ok := err.(syscall.Errno); ok {
            if errno == CRYPT_E_NOT_FOUND {
                break
            }
        }
        fmt.Println(syscall.GetLastError())
    }
    if cert == nil {
        break
    }
    // Copy the buf, since ParseCertificate does not create its own copy.
    buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
    buf2 := make([]byte, cert.Length)
    copy(buf2, buf)
    if c, err := x509.ParseCertificate(buf2); err == nil {
        for _, value := range c.ExtKeyUsage {
            if value == x509.ExtKeyUsageClientAuth {
                fmt.Println(c.Subject.CommonName)
                fmt.Println(c.Issuer.CommonName)
                certs = append(certs, c)
            }
        }
    }
}

这种方式检索到的证书确实来自智能卡。以后使用时,认证失败:

cer:= tls.Certificate{Certificate: [][]byte{certs[0].Raw}, Leaf: certs[0],}

tlsConfig := &tls.Config{
    Certificates:       []tls.Certificate{cer},
    RootCAs:            caCertPool,
    InsecureSkipVerify: true,
}

transport := &http.Transport{TLSClientConfig: tlsConfig}

client := http.Client{
    Timeout:   time.Minute * 2,
    Transport: transport,
}

我猜失败是意料之中的,因为我没有提供私钥。

Java (SunMSCAPI) 和 .NET 似乎在幕后使用智能卡上的私钥,例如我做的和上面几乎一样,并且身份验证“正常工作”。

有什么方法可以用 Go 实现这一点吗?

【问题讨论】:

    标签: go ssl smartcard client-certificates mutual-authentication


    【解决方案1】:

    您为 tls.Certificate 指定的私钥可以是任何实现 crypto.Signer 的对象,根据文档:

    是可用于签名操作的不透明私钥的接口。例如,保存在硬件模块中的 RSA 密钥。

    并且正是为这种用途而设计的。

    一旦您可以访问底层密钥,实现接口就相当简单了。例如,thalesignite/crypto11 为 PKCS#11 密钥提供了这样的实现。

    【讨论】:

      【解决方案2】:

      您可以使用供应商的 PKCS11 文件 + crypto11 库。

      package main
      
      import (
          "crypto/tls"
          "fmt"
          "io/ioutil"
          "log"
          "net/http"
          "github.com/ThalesIgnite/crypto11"
      )
      
      func main() {
          config := crypto11.Config{
              Path: "C:\\Windows\\System32\\vendor-pkcs11.dll",
              TokenSerial: "123456789456123",
              Pin: "123456",
          }
      
          context, err := crypto11.Configure(&config)
          if err != nil{
              log.Fatalln(err)
          }
      
          certificates, err := context.FindAllPairedCertificates()
          if err != nil{
              log.Fatalln(err)
          }
      
          fmt.Println("total certificates: ", len(certificates))
      
          cert := certificates[0]
          client := &http.Client{
              Transport: &http.Transport{
                  TLSClientConfig: &tls.Config{
                      Certificates: []tls.Certificate{cert},
                      Renegotiation:      tls.RenegotiateOnceAsClient,
                  },
              },
          }
      
          req, err := http.NewRequest("GET", "https://server.cryptomix.com:443/secure/", nil)
          if err != nil {
              log.Fatalln(err)
          }
      
          req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36")
      
          resp, err := client.Do(req)
          if err != nil {
              log.Fatalln(err)
          }
      
          fmt.Println("status code: ", resp.StatusCode)
      
          if resp.StatusCode == http.StatusOK {
              bodyBytes, err := ioutil.ReadAll(resp.Body)
              if err != nil {
                  log.Fatal(err)
              }
              bodyString := string(bodyBytes)
              fmt.Println(bodyString)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-06-05
        • 1970-01-01
        • 2010-12-11
        • 2019-05-13
        • 2021-10-20
        • 1970-01-01
        • 2017-01-02
        • 2018-11-12
        • 1970-01-01
        相关资源
        最近更新 更多