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

浪尽此生 提交于 2019-12-09 18:22:03

问题


Scenario

I am working on a Java Swing project, where I must develop a feature of listing certificates for users to choose for authentication via SSL against the server.

These certificates must contain the user imported ones in Firefox, and if a smartcard is inserted, those in the card will be listed, too. The environment is Linux/MacOS. In Windows the Internet Explorer handles it all, and what we would like to achieve is much like what happens in Windows: list all certificates, along with those in card, for users to choose.


Situation

When using NSS (Network Security Service) of Mozilla in Ubuntu, I found I am lost. With no code samples for using JSS in Java, I can only get it to work partially, depending on the way how I load the config file for the provider.

What I do now, is:

  1. read the cert in firefox (with KeyStore, Provider and KeyStore.Builder, loading the softokn.so as the library).

  2. Load the cert from card with CryptoManager and get all its modules. (CryptoManager.initialize(profileDir), cm.getModules(), module.getTokens(), etc. )


Problem

Approach 1

If I load the provider with libsoftoken3.so, I can see the user certificates. But, when I initialize the CryptoManager after constructing the provider, the external modules (e.g., my smart cards) are not listed in cryptoManager.getModules().

config = "library=" + NSS_JSS_Utils.NSS_LIB_DIR + "/libsoftokn3.so\n"
            + "name=\"Soft Token\"\n"
            + "slot=2\n" //for softoken, can only be 2.
            + "attributes=compatibility\n"
            + "allowSingleThreadedModules=true\n"
            + "showInfo=true\n"
            + "nssArgs=\"configdir='" + NSS_JSS_Utils.getFireFoxProfilePath() + "' "
                + "certPrefix='' "
                + "keyPrefix='' "
                + "secmod='secmod.db' "
                + "flags='readOnly'\""
//              + "flags='noDb'\""
            + "\n";

Approach 2

If I load the provider with NNS's secmod.db, the card will be listed, even if it's not present/inserted, in the keyStore constructed with this provider. When it's inserted, in the second step above, I can see the external modules, but then the card is listed twice, with the same alias.

config = "name=\"NSS Module\"\n"
            + "attributes=compatibility\n"
            + "showInfo=true\n"
            + "allowSingleThreadedModules=true\n"
            + "nssUseSecmod=true\n"
            + "nssSecmodDirectory=" + NSS_JSS_Utils.getFireFoxProfilePath();

Question:

  • How can I easily load all certificate in a simple way, not separately with JSS?

  • If it's not possible, how can I configure the provider to load them separately but without repetition?


回答1:


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.


来源:https://stackoverflow.com/questions/40653233/nss-jss-load-user-imported-cert-along-with-pkcs11-smartcard-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!