To perform client certificate authentication (mutual authentication) all examples I've found assume that a private key is accessible (e.g. from a file). A certificate containing private and public key is generated like this:
cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
Now, I have to get the certificate (and private key, which as far as I know can't be extracted - signing should be done via PKCS#11) from a SmartCard. So far I was able to enumerate the certificates from the Windows certificate store:
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)
}
}
}
}
The this way retrieved certificate is indeed from the SmartCard. When using it later on, the authentication fails:
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,
}
I guess the failure is to be expected as I didn't provide a private key.
Java (SunMSCAPI)and .NET seem to under the covers use the private key on the SmartCard, e.g. I do pretty much the same as above and the authentication "just works".
Is there any way to achieve this with Go?
The private key you specify for your tls.Certificate
can be any object that implements crypto.Signer
which, per the documentation:
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.
and is intended exactly for this kind of use.
Implementing the interface is fairly straightforward once you have access to the underlying key. thalesignite/crypto11 provides such an implementation for PKCS#11 keys, for example.