Go:使用存储在智能卡上的客户端证书的 HTTPS 请求 (Windows)
Go: HTTPS Request using a Client Certificate stored on a SmartCard (Windows)
为了执行客户端证书身份验证(相互身份验证),我发现的所有示例都假设私钥是可访问的(例如,从文件中)。包含私钥和 public 密钥的证书生成如下:
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 似乎在幕后使用智能卡上的私钥,例如我做的和上面的差不多,认证 "just works".
有什么方法可以用 Go 来实现吗?
您为 tls.Certificate
指定的私钥可以是任何实现 crypto.Signer
的对象,根据文档:
is an interface for an opaque private key that can be used for signing operations. For example, an RSA key kept in a hardware module.
并且正是为这种用途而设计的。
一旦您有权访问基础密钥,实现接口就相当简单了。例如,thalesignite/crypto11 为 PKCS#11 密钥提供了这样的实现。
您可以使用供应商的 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)
}
}
为了执行客户端证书身份验证(相互身份验证),我发现的所有示例都假设私钥是可访问的(例如,从文件中)。包含私钥和 public 密钥的证书生成如下:
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 似乎在幕后使用智能卡上的私钥,例如我做的和上面的差不多,认证 "just works".
有什么方法可以用 Go 来实现吗?
您为 tls.Certificate
指定的私钥可以是任何实现 crypto.Signer
的对象,根据文档:
is an interface for an opaque private key that can be used for signing operations. For example, an RSA key kept in a hardware module.
并且正是为这种用途而设计的。
一旦您有权访问基础密钥,实现接口就相当简单了。例如,thalesignite/crypto11 为 PKCS#11 密钥提供了这样的实现。
您可以使用供应商的 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)
}
}