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.
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:
read the cert in firefox (with KeyStore
, Provider
and KeyStore.Builder
, loading the softokn.so
as the library).
Load the cert from card with CryptoManager
and get all its modules.
(CryptoManager.initialize(profileDir)
, cm.getModules()
, module.getTokens()
, etc. )
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";
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?
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.
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
.
Insert the smartcard before the first time CryptoManager
is initialized. (If not, the card will be ignored until restarting the app.)
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. **
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();
....
.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();
List the alias of the certificates we get from cardKeyStore
in the same JTable
we used above, along with those of softKeyStore
.
When the user select a row in the JTable
, get the selected alias and store it in a static field.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With