设想
我正在执行Java Swing项目,在该项目中,我必须开发一种列出证书的功能,以便用户选择针对服务器通过SSL进行身份验证。
这些证书必须包含用户在Firefox中导入的证书,如果插入了智能卡,则还将列出该卡中的证书。环境是Linux/MacOS。在Windows中,Internet Explorer可以处理所有事情,我们想要实现的功能与Windows中的情况非常相似:列出所有证书以及卡中的证书,以供用户选择。
情况
在Ubuntu中使用Mozilla的NSS(网络安全服务)时,我迷路了。由于没有用于在Java中使用JSS的代码示例,所以只能使其部分工作,具体取决于我如何为提供程序加载配置文件。
我现在要做的是:
KeyStore
,Provider
和KeyStore.Builder
,将softokn.so
加载为库)。 CryptoManager
从卡中加载证书,并获取其所有模块。(
CryptoManager.initialize(profileDir)
,cm.getModules()
,module.getTokens()
等)问题
方法1
如果我使用
libsoftoken3.so
加载提供程序,则可以看到用户证书。但是,当我在构造CryptoManager
后初始化provider
时,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";
方法2
如果我将NNS的
secmod.db
加载到提供程序,则该卡将在使用此keyStore
构建的provider
中列出,即使不存在/未插入,也将列出。插入后,在上面的第二步中,我可以看到外部模块,但是随后该卡以相同的别名列出了两次。config = "name=\"NSS Module\"\n"
+ "attributes=compatibility\n"
+ "showInfo=true\n"
+ "allowSingleThreadedModules=true\n"
+ "nssUseSecmod=true\n"
+ "nssSecmodDirectory=" + NSS_JSS_Utils.getFireFoxProfilePath();
问题:
最佳答案
我已经以某种方式成功解决了它。我有80%的问题是由我自己解决的.....我认为这很正常。
基本上,它包括构造两个KeyStore
实例和两个Provider
实例,每个实例一个,用于用户证书,另一个用于智能卡。
libsoftokn.so
构造一个提供程序,例如,我问题中的第一个config
,然后将其插入。使用KeyStore.Builder
和此提供程序,构建一个KeyStore softKeyStore
。在此 keystore 中,您拥有所有用户证书。提取这些证书的信息,并在JTable
中列出它们。 CryptoManager
之前插入智能卡。 (否则,卡将被忽略,直到重新启动应用程序为止。)CryptoManager
。这里有一些技巧可以打破AlreadyInitializedException
/NotInitializedException
的死循环:我们有:
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.
}
}
}
现在,我们可以执行
cm.getModules()
和module.getTokens()
来识别卡。 **只有插入卡后,相关模块及其 token 才会出现。 **InternalCryptoToken
和InternalKeyStorageToken
。 所以:
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()) {
....
}
登录 token 后,使用
Runtime.getRuntime().exec(command)
执行shell脚本以使用NSS附带的modutil
。在 shell 中是这样的:
modutil -dbdir /your/firefox/profile/dir -rawlist
此命令以可读格式显示
secmod.db
中包含的信息。 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 ] } "
因此,您可以分析输出并在
module.getName()
所在的行中获取库位置。我们可以使用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
驱动程序的文件路径,构造一个config
字符串以加载另一个提供程序。 我们将有:
String config = "name=\"" + moduleName + "\"\n" + "library=" + libPath;
moduleName
最好用“\”转义,因为它通常包含空格。如果需要空格,应将libPath
转义。最好不要有空格。插入此提供程序,并使用相同的提供程序构造一个
cardKeyStore
。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();
cardKeyStore
获得的证书的别名(与上面使用的JTable
相同),以及softKeyStore
的证书的别名。 JTable
中选择一行时,获取所选别名并将其存储在静态字段中。 KeyManagerFactory
和X509KeyManager
进行SSL通信时,使用静态alias
我们先在softKeyStore
中查找它,然后在cardKeyStore
中查找它。 喜欢:
if (softKeyStore.containsAlias(alias)) {
return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
return cardKeyStore;
}