Search code examples
javadigital-signaturedigital-certificate

Signing documents with SunMSCAPI and suppressing the "Enter PIN" dialog


I am developing a java code that signs documents using a certificate token. So far, everything works great, but I want to suppress the "enter pin" dialog because I am storing the user's pin so he/she does not need to type it every time. The real problem here is that this code will run in batch mode (no user interaction). I know that once typed, the key may be in memory so it does not need to be typed again for a shorty time. But I can't rely on that, I need to provide the PIN. Here the code I have so far (it is only a sample, it may not be complete nor work):

protected KeyStore loadKeyStoreFromSmartCard()  {
  keyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
  keyStore.load(null, null);
  return keyStore;
}

public void signDocument(byte[] conteudoParaAssinar, String certAlias) {
    char[] pass = (char[]) null;
    PrivateKey key = (PrivateKey) loadKeyStoreFromSmartCard.getKey(certAlias, pass);
    Certificate[] chain = loadKeyStoreFromSmartCard(true).getCertificateChain(certAlias);
    CertStore certsAndCRLs = CertStore.getInstance("Collection", new CollectionCertStoreParameters(Arrays.asList(chain)), "BC");
    X509Certificate cert = (X509Certificate) chain[0];
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    gen.addSigner(key, cert, CMSSignedDataGenerator.DIGEST_SHA1);
    gen.addCertificatesAndCRLs(certsAndCRLs);
    CMSProcessable data = new CMSProcessableByteArray(conteudoParaAssinar);
    CMSSignedData signed = gen.generate(data, true, "SunMSCAPI");
    byte[] envHex = signed.getEncoded();
}

EDIT

I have heard about CryptSetProvParam PP_KEYEXCHANGE_PIN witch may be the solution, but I dont know how to call it from java. All examples I have found are for .net.


Solution

  • I implemented something similar to this once, but unfortunately the smart card driver was buggy and so the driver tried to bring up the native PIN callback implemented in the driver itself at times. But let's assume your driver does better at that.

    First of all, you need to implement a CallbackHandler, the documentation gives a good overview of the concept. In your case, it's the PasswordCallback case that's interesting to handle.

    Next, create your KeyStore as follows (exception handling omitted)

    Provider provider = Security.getProvider("SunMSCAPI");
    CallbackHandler cbh = // your implementation
    KeyStore.ProtectionParameter protection = new KeyStore.CallbackHandlerProtection(cbh);
    //get a handle of the CAPI KeyStore as before
    KeyStore.Builder keystoreBuilder = KeyStore.Builder.newInstance("Windows-MY",
                                                                    provider, 
                                                                    protection);
    KeyStore store = keystoreBuilder.getKeyStore();
    

    Then, to access the private key, do this:

    KeyStore.Entry ke = store.getEntry(alias, null);
    if (!(ke instanceof KeyStore.PrivateKeyEntry))
        throw new RuntimeException("The entry is not a private key.");
    PrivateKey key = ((KeyStore.PrivateKeyEntry) ke).getPrivateKey();
    

    The provider will automatically generate the appropriate PasswordCallbacks to be sent to your CallbackHandler. When handling the callback, you would simply pass your cached password.

    Needless to say that password caching is generally frowned upon ;)