问题
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:
read the cert in firefox (with
KeyStore
,Provider
andKeyStore.Builder
, loading thesoftokn.so
as the library).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.
Construct a provider with
libsoftokn.so
, e.g., the firstconfig
in my question, and insert it. WithKeyStore.Builder
and this provider, build aKeyStore softKeyStore
. In this keystore you have all the user certificates. Extract the information of these certificates and list them in aJTable
.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 ofAlreadyInitializedException
/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. **
- 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
andInternalKeyStorageToken
.
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();
....
- Then, with the file path of the
.so
driver, construct aconfig
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 sameJTable
we used above, along with those ofsoftKeyStore
.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
andX509KeyManager
for SSL communication, with the staticalias
we look it up insoftKeyStore
and thencardKeyStore
.
Like:
if (softKeyStore.containsAlias(alias)) {
return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
return cardKeyStore;
}
- SSL handshake, sending messages, receiving, signing, etc.
来源:https://stackoverflow.com/questions/40653233/nss-jss-load-user-imported-cert-along-with-pkcs11-smartcard-in-java