Search code examples
gosslsmartcardclient-certificatesmutual-authentication

Go: HTTPS Request using a Client Certificate stored on a SmartCard (Windows)


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?


Solution

  • 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.