NSS/JSS:在 Java 中加载用户导入的证书以及 PKCS#11 智能卡
NSS/JSS: load user imported cert along with PKCS#11 smartcard in Java
场景
我正在开发一个 Java Swing 项目,我必须开发一个列出证书的功能,供用户选择通过 SSL 针对服务器进行身份验证。
这些证书必须包含用户在 Firefox 中导入的证书,如果插入智能卡,卡中的证书也会被列出。环境是Linux/MacOS。在 Windows 中,Internet Explorer 会处理这一切,我们想要实现的与 Windows 中发生的事情非常相似:列出所有证书以及卡中的证书,供用户选择。
情况
在Ubuntu中使用Mozilla的NSS(Network Security Service)时,我发现自己迷路了。由于没有在 Java 中使用 JSS 的代码示例,我只能让它部分工作,具体取决于我为提供程序加载配置文件的方式。
我现在做的是:
在 firefox 中读取证书(使用 KeyStore
、Provider
和 KeyStore.Builder
,将 softokn.so
作为库加载)。
使用CryptoManager
从卡中加载证书并获取其所有模块。
(CryptoManager.initialize(profileDir)
, cm.getModules()
, module.getTokens()
, 等等)
问题
方法一
如果我使用 libsoftoken3.so
加载提供程序,我可以看到用户证书。但是,当我在构造 provider
之后初始化 CryptoManager
时,外部模块(例如,我的智能卡)没有在 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";
方法二
如果我用 NNS 的 secmod.db
加载提供程序,该卡将被列出,即使它不是 present/inserted,在 keyStore
用这个 provider
构造。插入后,在上面的第二步中,我可以看到外部模块,但是该卡列出了两次,别名相同。
config = "name=\"NSS Module\"\n"
+ "attributes=compatibility\n"
+ "showInfo=true\n"
+ "allowSingleThreadedModules=true\n"
+ "nssUseSecmod=true\n"
+ "nssSecmodDirectory=" + NSS_JSS_Utils.getFireFoxProfilePath();
问题:
如何以简单的方式轻松加载所有证书,而不是单独使用 JSS?
如果不可能,我如何配置提供程序以单独加载它们而不重复?
我已经以某种方式成功地解决了它。我80%的问题都是自己解决的…………我觉得很正常
基本上它包括构造KeyStore
的两个实例和Provider
的两个实例,一个用于用户证书,另一个用于智能卡。
用 libsoftokn.so
构建一个提供者,例如我问题中的第一个 config
,然后插入它。使用 KeyStore.Builder
和此提供程序,构建一个 KeyStore softKeyStore
。在此密钥库中,您拥有所有用户证书。提取这些证书的信息并将它们列在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()
来识别卡片。 **只有当卡被插入时,相关模块及其令牌才会出现。 **
- 当我们到达卡的令牌时,检查它是否需要登录以及是否已登录。并且,我们必须排除
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()) {
....
}
登录令牌后,使用 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
字符串,加载另一个provider。
我们将有:
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();
在上面使用的 JTable
中列出我们从 cardKeyStore
获得的证书的别名,以及 softKeyStore
的别名。
当用户 select 在 JTable
中的一行时,获取 selected 别名并将其存储在静态字段中。
当我们需要一个密钥库来构造KeyManagerFactory
和X509KeyManager
用于SSL通信时,我们使用静态alias
在softKeyStore
中查找它然后 cardKeyStore
.
赞:
if (softKeyStore.containsAlias(alias)) {
return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
return cardKeyStore;
}
- SSL 握手、发送消息、接收、签名等
场景
我正在开发一个 Java Swing 项目,我必须开发一个列出证书的功能,供用户选择通过 SSL 针对服务器进行身份验证。
这些证书必须包含用户在 Firefox 中导入的证书,如果插入智能卡,卡中的证书也会被列出。环境是Linux/MacOS。在 Windows 中,Internet Explorer 会处理这一切,我们想要实现的与 Windows 中发生的事情非常相似:列出所有证书以及卡中的证书,供用户选择。
情况
在Ubuntu中使用Mozilla的NSS(Network Security Service)时,我发现自己迷路了。由于没有在 Java 中使用 JSS 的代码示例,我只能让它部分工作,具体取决于我为提供程序加载配置文件的方式。
我现在做的是:
在 firefox 中读取证书(使用
KeyStore
、Provider
和KeyStore.Builder
,将softokn.so
作为库加载)。使用
CryptoManager
从卡中加载证书并获取其所有模块。 (CryptoManager.initialize(profileDir)
,cm.getModules()
,module.getTokens()
, 等等)
问题
方法一
如果我使用 libsoftoken3.so
加载提供程序,我可以看到用户证书。但是,当我在构造 provider
之后初始化 CryptoManager
时,外部模块(例如,我的智能卡)没有在 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";
方法二
如果我用 NNS 的 secmod.db
加载提供程序,该卡将被列出,即使它不是 present/inserted,在 keyStore
用这个 provider
构造。插入后,在上面的第二步中,我可以看到外部模块,但是该卡列出了两次,别名相同。
config = "name=\"NSS Module\"\n"
+ "attributes=compatibility\n"
+ "showInfo=true\n"
+ "allowSingleThreadedModules=true\n"
+ "nssUseSecmod=true\n"
+ "nssSecmodDirectory=" + NSS_JSS_Utils.getFireFoxProfilePath();
问题:
如何以简单的方式轻松加载所有证书,而不是单独使用 JSS?
如果不可能,我如何配置提供程序以单独加载它们而不重复?
我已经以某种方式成功地解决了它。我80%的问题都是自己解决的…………我觉得很正常
基本上它包括构造KeyStore
的两个实例和Provider
的两个实例,一个用于用户证书,另一个用于智能卡。
用
libsoftokn.so
构建一个提供者,例如我问题中的第一个config
,然后插入它。使用KeyStore.Builder
和此提供程序,构建一个KeyStore softKeyStore
。在此密钥库中,您拥有所有用户证书。提取这些证书的信息并将它们列在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()
来识别卡片。 **只有当卡被插入时,相关模块及其令牌才会出现。 **
- 当我们到达卡的令牌时,检查它是否需要登录以及是否已登录。并且,我们必须排除
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()) {
....
}
登录令牌后,使用 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
字符串,加载另一个provider。
我们将有:
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();
在上面使用的
JTable
中列出我们从cardKeyStore
获得的证书的别名,以及softKeyStore
的别名。当用户 select 在
JTable
中的一行时,获取 selected 别名并将其存储在静态字段中。当我们需要一个密钥库来构造
KeyManagerFactory
和X509KeyManager
用于SSL通信时,我们使用静态alias
在softKeyStore
中查找它然后cardKeyStore
.
赞:
if (softKeyStore.containsAlias(alias)) {
return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
return cardKeyStore;
}
- SSL 握手、发送消息、接收、签名等