NSS/JSS:在Java中加载用户导入的证书以及PKCS#11智能卡

Wes*_*Gun 5 java nss client-certificates

脚本

我正在开发一个Java Swing项目,我必须开发一个列出证书的功能,供用户选择通过SSL对服务器进行身份验证.

这些证书必须包含Firefox中用户导入的证书,如果插入了智能卡,也会列出卡中的智能卡.环境是Linux/MacOS.在Windows中,Internet Explorer处理所有这些,我们希望实现的与Windows中发生的情况非常相似:列出所有证书以及卡中的证书,供用户选择.


情况

在Ubuntu中使用Mozilla的NSS(网络安全服务)时,我发现我迷路了.由于没有在Java中使用JSS的代码示例,我只能部分地使用它,具体取决于我为提供程序加载配置文件的方式.

我现在做的是:

  1. 在firefox中读取证书(使用KeyStore,Provider并将KeyStore.Builder其softokn.so作为库加载).

  2. 从卡中加载证书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";
Run Code Online (Sandbox Code Playgroud)

方法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();
Run Code Online (Sandbox Code Playgroud)

题:

  • 如何以简单的方式轻松加载所有证书,而不是单独使用JSS?

  • 如果不可能,我如何配置提供程序以单独加载它们但不重复?

Wes*_*Gun 1

我以某种方式成功地解决了这个问题。我80%的问题都是自己解决的……我觉得很正常。

基本上,它包括构造 的两个实例KeyStore和 的两个实例Provider,每个实例对应一个,一个用于用户证书,另一个用于智能卡。

  1. 构造一个提供者libsoftokn.so,例如config我问题中的第一个,然后插入它。使用KeyStore.Builder此提供程序,构建一个KeyStore softKeyStore. 在此密钥库中,您拥有所有用户证书。提取这些证书的信息并将它们列在JTable.

  2. 首次CryptoManager初始化前插入智能卡。(如果没有,该卡将被忽略,直到重新启动应用程序为止。)

  3. 初始化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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我们可以执行cm.getModules()和module.getTokens(),来识别该卡。**只有当卡插入时,相关模块及其令牌才会出现。**

  1. 当我们获得卡的令牌时,检查它是否需要登录以及是否已记录。并且,我们必须排除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()) {
                ....
            }
Run Code Online (Sandbox Code Playgroud)

当令牌登录时,执行shell脚本以Runtime.getRuntime().exec(command)使用modutilNSS 附带的功能。

在shell中是这样的:

modutil -dbdir /your/firefox/profile/dir -rawlist
Run Code Online (Sandbox Code Playgroud)

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 ] }  "
Run Code Online (Sandbox Code Playgroud)

因此,您可以分析输出并在您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();
....
Run Code Online (Sandbox Code Playgroud)
  1. 然后,使用驱动程序的文件路径.so,构造一个config字符串来加载另一个提供程序。

我们将有:

String config = "name=\"" + moduleName + "\"\n" + "library=" + libPath;
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)
  1. cardKeyStore列出我们从上面使用的相同证书中获得的证书的别名JTable,以及softKeyStore.

  2. 当用户在 中选择一行时JTable,获取所选别名并将其存储在静态字段中。

  3. 当我们需要一个密钥库来构建 KeyManagerFactorySSLX509KeyManager通信时,我们可以使用 static 来alias查找它softKeyStore,然后cardKeyStore.

喜欢:

if (softKeyStore.containsAlias(alias)) {
    return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
    return cardKeyStore;
}
Run Code Online (Sandbox Code Playgroud)
  1. SSL握手、发送消息、接收、签名等