NSS/JSS: load user imported cert along with PKCS#11 smartcard in Java

这一生的挚爱 提交于 2019-12-04 08:32:40

I have somehow succeeded in solving it. 80% of my questions are solved by myself.........I think it's normal.

Basically it consists in constructing two instances of KeyStore and two instances of Provider, each for each, one for user certificates and the other for smartcard.

  1. Construct a provider with libsoftokn.so, e.g., the first config in my question, and insert it. With KeyStore.Builder and this provider, build a KeyStore softKeyStore. In this keystore you have all the user certificates. Extract the information of these certificates and list them in a JTable.

  2. Insert the smartcard before the first time CryptoManager is initialized. (If not, the card will be ignored until restarting the app.)

  3. Initialize CryptoManager. Here some tricks to break the dead loop of AlreadyInitializedException/NotInitializedException:

We have:

private static void initializeCryptoManager() throws Exception {
    //load the NSS modules before creating the second keyStore.

    if (cm == null) { //cm is of type CryptoManager
        while (true) { //the trick.
            try {
                cm = CryptoManager.getInstance();
            } catch (NotInitializedException e2) {
                try {
                    InitializationValues iv = new InitializationValues(NSS_JSS_Utils.getFireFoxProfilePath());
                    //TEST
                    iv.installJSSProvider = false;
                    iv.removeSunProvider = false;
                    iv.initializeJavaOnly = false; //must be false, or native C error if no provider is created.
                    iv.cooperate = false;
                    iv.readOnly = true;
                    iv.noRootInit = true;
                    iv.configDir = NSS_JSS_Utils.getFireFoxProfilePath();
                    iv.noModDB = false;
    //              iv.noCertDB = false; 
    //              CustomPasswordCallback cpc = new  CustomPasswordCallback();
    //              iv.passwordCallback = cpc; //no passwordcallback needed here.
                    iv.forceOpen = false;
                    iv.PK11Reload = false;
                    CryptoManager.initialize(iv);
                    continue; // continue to getInstance.
                } catch (KeyDatabaseException | CertDatabaseException | GeneralSecurityException e) {
                    Traza.error(e);
                    throw e;
                } catch (AlreadyInitializedException e1) {
                    continue; //if is initialized, must go on to get cm.
                }
            } 
            break; //if nothing is catched, must break to end the loop.
        }
    }
}

And now, we can do cm.getModules() and module.getTokens(), to recognize the card. **Only when the card is inserted, the relevant module and its token will be present. **

  1. When we get to the token of the card, check if it needs login and if it's logged. And, we must exclude the InternalCryptoToken and InternalKeyStorageToken.

So:

if (!token.isInternalCryptoToken() && !token.isInternalKeyStorageToken()){ // If not Internal Crypto service, neither Firefox CA store
    if (token.isPresent() ) { // when the card is inserted
        if (!token.isLoggedIn()){ // Try to login. 3 times.
            Traza.info("Reading the certificates from token " + token.getName() + ". Loggining... ");
            while (UtilTarjetas.tries <= 3) {
                try {
                //TEST
                    token.setLoginMode(NSS_JSS_Utils.LOGIN_MODE_ONE_TIME); 
                    token.login((PasswordCallback) new CustomPasswordCallback());
                    UtilTarjetas.prevTryFailed = false;
                    cm.setThreadToken(token);
                    break;
                } catch (IncorrectPasswordException e){
                    UtilTarjetas.prevTryFailed = true;
                    UtilTarjetas.tries ++;
                } catch (TokenException e) {
                    UtilTarjetas.prevTryFailed = true;
                    UtilTarjetas.tries ++;
                } 
            }

                // if tries > 3
                if (UtilTarjetas.tries > 3) {
                    Traza.error("The token " + token.getName() + " is locked now. ");
                    throw new IOException("You have tries 3 times and now the card is locked. ");
                }
            }

            if (token.isLoggedIn()) {
                ....
            }

When the token is logged in, execute shell script with Runtime.getRuntime().exec(command) to use modutil shipped with NSS.

In shell it's like this:

modutil -dbdir /your/firefox/profile/dir -rawlist

This command shows you information contained in secmod.db in readable format.

 name="NSS Internal PKCS #11 Module" parameters="configdir=/home/easternfox/.mozilla/firefox/5yasix1g.default-1475600224376 certPrefix= keyPrefix= secmod=secmod.db flags=readOnly " NSS="trustOrder=75 cipherOrder=100 slotParams={0x00000001=[slotFlags=RSA,RC4,RC2,DES,DH,SHA1,MD5,MD2,SSL,TLS,AES,SHA256,SHA512,Camellia,SEED,RANDOM askpw=any timeout=30 ] 0x00000002=[ askpw=any timeout=0 ] }  Flags=internal,critical"

library=/usr/lib/libpkcs11-dnie.so name="DNIe NEW"  

library=/usr/local/lib/libbit4ipki.so name="Izenpe local"  NSS="  slotParams={0x00000000=[ askpw=any timeout=0 ] }  "

So you can analyze the output and get the library location in the line where your module.getName() are located. We can use StringTokenizer.

//divide the line into strings with "=".
StringTokenizer tz = new StringTokenizer(line, "=");
//get the first part, "library". 
String token = tz.nextToken(); 
//get the second part, "/usr/local/lib/libbit4ipki.so name"
token = tz.nextToken();
....
  1. Then, with the file path of the .so driver, construct a config string to load another provider.

We will have:

String config = "name=\"" + moduleName + "\"\n" + "library=" + libPath;

moduleName is better escaped with "\", because it usually contains spaces. libPath should be escaped, if there would like to be spaces. Better not to have spaces.

Insert this provider, and construct a cardKeyStore with the same provider.

Provider p = new SunPKCS11(new ByteArrayInputStream(config.getBytes()));
Security.insertProviderAt(p, 1);
KeyStore.Builder builder = null;
builder = KeyStore.Builder.newInstance("PKCS11", p, 
    new KeyStore.CallbackHandlerProtection(new UtilTarjetas().new CustomCallbackHandler()));
cardKeyStore = builder.getKeyStore();
  1. List the alias of the certificates we get from cardKeyStore in the same JTable we used above, along with those of softKeyStore.

  2. When the user select a row in the JTable, get the selected alias and store it in a static field.

  3. When we need a keystore to construct KeyManagerFactory and X509KeyManager for SSL communication, with the static alias we look it up in softKeyStore and then cardKeyStore.

Like:

if (softKeyStore.containsAlias(alias)) {
    return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
    return cardKeyStore;
}
  1. SSL handshake, sending messages, receiving, signing, etc.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!