Go:使用存储在智能卡上的客户端证书进行 HTTPS 请求 (Windows)

Kna*_*ack 5 ssl client-certificates go smartcard mutual-authentication

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

cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
Run Code Online (Sandbox Code Playgroud)

现在,我必须从智能卡获取证书(和私钥,据我所知无法提取 - 签名应通过 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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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,
}
Run Code Online (Sandbox Code Playgroud)

我想失败是可以预料的,因为我没有提供私钥。

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

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

Cro*_*man 4

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

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

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

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