Yuri Schimke
Yuri Schimke

Reputation: 13448

Java SSL client not selecting a smartcard key

I'm trying to use an Estonian ID card for SSL client authentication in a java program. This works in Chrome/Firefox against both banking sites and test servers (nginx or openssl s_server).

However my Java client (okhttp) works fine for a local keystore and fails when trying to use the ID card. I've boiled it down to this test case which reproduces the problem I see in a debugger and with logging (-Djavax.net.debug=ssl:handshake).

I can communicate with the card, e.g. I can print out the certificates for the same key. I'm piggybacking on the DigiDoc3 Client on Mac OSX.

I can see the exception that seems to cause the key to be ignored

sun.security.pkcs11.wrapper.PKCS11Exception: CKR_ATTRIBUTE_TYPE_INVALID
    at sun.security.pkcs11.wrapper.PKCS11.C_GetAttributeValue(Native Method)
    at sun.security.pkcs11.P11Key.getAttributes(P11Key.java:275)
    at sun.security.pkcs11.P11Key.privateKey(P11Key.java:330)
    at sun.security.pkcs11.P11KeyStore.loadPkey(P11KeyStore.java:1311)
    at sun.security.pkcs11.P11KeyStore.engineGetEntry(P11KeyStore.java:943)
    at java.security.KeyStore.getEntry(KeyStore.java:1521)
    at sun.security.ssl.X509KeyManagerImpl.getEntry(X509KeyManagerImpl.java:276)
    at sun.security.ssl.X509KeyManagerImpl.getCertificateChain(X509KeyManagerImpl.java:107)
    at com.baulsupp.oksocial.TestMain.main(TestMain.java:37)

Test program output

1.0.Authentication
ssl: KeyMgr: choosing key: Authentication (verified: OK)
null
null

Test code

package com.baulsupp.oksocial;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;

public class TestMain {
  public static void main(String[] args)
      throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
      KeyStoreException, IOException {
    System.setProperty("javax.net.debug", "all");

    char[] password =
        System.getenv().get("PW").toCharArray();//System.console().readPassword("PW: ");

    X509ExtendedKeyManager km = (X509ExtendedKeyManager) getKeyManagers(password, 0)[0];

    String alias = km.chooseClientAlias(new String[] {"RSA"}, null, null);

    System.out.println(alias);

    X509Certificate[] chain = km.getCertificateChain(alias);
    System.out.println(chain);

    PrivateKey key = km.getPrivateKey(alias);
    System.out.println(key);
  }

  public static KeyManager[] getKeyManagers(char[] password, int slot)
      throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
      UnrecoverableKeyException {
    //Security.removeProvider("IAIK");

    //Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
    //Security.addProvider(provider);

    String config =
        "name=OpenSC\nlibrary=/Applications/qdigidocclient.app/Contents/MacOS/esteid-pkcs11.so\nslotListIndex="
            + slot;

    sun.security.pkcs11.SunPKCS11 pkcs11 =
        new sun.security.pkcs11.SunPKCS11(new ByteArrayInputStream(config.getBytes()));

    Security.addProvider(pkcs11);

    //debugProviders();

    KeyStore keystore = KeyStore.getInstance("PKCS11", pkcs11);

    keystore.load(null, password);

    //debugKeys(keystore);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509");
    kmf.init(keystore, null);

    return kmf.getKeyManagers();
  }

  public static void debugKeys(KeyStore keystore) throws KeyStoreException {
    Enumeration<String> aliases = keystore.aliases();

    while (aliases.hasMoreElements()) {
      String s = aliases.nextElement();

      Certificate k = keystore.getCertificate(s);

      System.out.println(k);
    }
  }

  public static void debugProviders() {
    Provider[] providers = Security.getProviders();
    for (Provider p : providers) {
      System.out.println("\n\n" + p.getName());
      Set<Provider.Service> services = p.getServices();

      for (Provider.Service s : services) {
        System.out.println(s.getType() + " " + s.getAlgorithm());
      }
    }
  }
}

In the mean time I've put in a support request as well.

Upvotes: 3

Views: 1722

Answers (3)

Yuri Schimke
Yuri Schimke

Reputation: 13448

A similar example for a Yubikey https://github.com/square/okhttp/blob/97a41eb5c2c97f29fa7152e43ae3a480dc53e0fd/samples/guide/src/main/java/okhttp3/recipes/kt/YubikeyClientAuth.kt

    val config = "--name=OpenSC\nlibrary=/Library/OpenSC/lib/opensc-pkcs11.so\nslot=$slot\n"

    // May fail with ProviderException with root cause like
    // sun.security.pkcs11.wrapper.PKCS11Exception: CKR_SLOT_ID_INVALID
    val pkcs11 = Security.getProvider("SunPKCS11").configure(config)
    Security.addProvider(pkcs11)

    val callbackHandler = ConsoleCallbackHandler

    val builderList: List<KeyStore.Builder> = Arrays.asList(
        KeyStore.Builder.newInstance("PKCS11", null, KeyStore.CallbackHandlerProtection(callbackHandler))

        // Example if you want to combine multiple keystore types
        // KeyStore.Builder.newInstance("PKCS12", null, File("keystore.p12"), PasswordProtection("rosebud".toCharArray()))
    )

    val keyManagerFactory = KeyManagerFactory.getInstance("NewSunX509")
    keyManagerFactory.init(KeyStoreBuilderParameters(builderList))
    val keyManager = keyManagerFactory.keyManagers[0] as X509ExtendedKeyManager

Upvotes: 0

Yuri Schimke
Yuri Schimke

Reputation: 13448

Based solely on Martin's answer, building https://github.com/OpenSC/OpenSC from source and installing OpenSC-0.15.0.dmg gave an alternative driver that worked first time. I didn't use the pkcs11-spy because it just worked first time.

String config =
    "name=OpenSC\n" +
        "library=/Library/OpenSC/lib/opensc-pkcs11.so\n";

I was able to test against openssl

$ openssl s_server -verify 20 -key key.pem -cert cert.pem -accept 44330   -no_ssl3 -dhparam dhparam.pem -www 

Which responded with

---
Client certificate
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            xxxxxxxxx
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=EE, O=AS Sertifitseerimiskeskus, CN=ESTEID-SK 2011/[email protected]
        Validity
            Not Before: Jul 15 09:51:27 20xx GMT
            Not After : Jul 13 20:59:59 20xx GMT
        Subject: C=EE, O=ESTEID, OU=authentication, CN=SCHIMKE,YURI,xxxxxxxx, SN=SCHIMKE, GN=YURI/serialNumber=xxxxxxxx

Upvotes: 1

Martin Paljak
Martin Paljak

Reputation: 4142

AFAIK the attributes only matter if you create objects in the PKCS#11 module, which is not the case with the smart card. Try to see with pkcs11-spy from OpenSC what kind of attributes are asked for and not implemented by the module.

Another option is to use the lower-layer PKCS#11 methods (C_*), which gives better control of PKCS#11 details.

Upvotes: 1

Related Questions