NSS/JSS: загрузить импортированный пользователем сертификат вместе со смарт-картой PKCS#11 в Java

Сценарий

Я работаю над проектом Java Swing, где я должен разработать функцию перечисления сертификатов, чтобы пользователи могли выбрать аутентификацию через SSL на сервере.

Эти сертификаты должны содержать сертификаты, импортированные пользователем в Firefox, а если вставлена ​​смарт-карта, то сертификаты на карте также будут перечислены. Среда — Linux/MacOS. В Windows всем этим занимается Internet Explorer, и то, что мы хотели бы достичь, во многом похоже на то, что происходит в Windows: список всех сертификатов, а также тех, которые находятся в карточке, для выбора пользователями.


Ситуация

При использовании NSS (службы сетевой безопасности) Mozilla в Ubuntu я обнаружил, что потерялся. Без примеров кода для использования JSS в Java я могу заставить его работать только частично, в зависимости от того, как я загружаю файл конфигурации для провайдера.

Что я делаю сейчас, это:

  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";

Подход 2

Если я загружу провайдера secmod.db NNS, карта будет указана, даже если она отсутствует/вставлена, в 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?

  • Если это невозможно, то как мне настроить провайдера, чтобы они загружались отдельно, но без повторения?


person WesternGun    schedule 17.11.2016    source источник


Ответы (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.
        }
    }
}

И теперь мы можем сделать cm.getModules() и module.getTokens(), чтобы распознать карту. **Только когда карта вставлена, соответствующий модуль и его токен будут присутствовать. **

  1. Когда мы доберемся до токена карты, проверяем, нужен ли логин и зарегистрирован ли он. И мы должны исключить InternalCryptoToken и 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()) {
                ....
            }

Когда токен войдет в систему, выполните сценарий оболочки с Runtime.getRuntime().exec(command), чтобы использовать modutil, поставляемый с NSS.

В шелле это так:

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();
....
  1. Затем, используя путь к файлу драйвера .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();
  1. Перечислите псевдонимы сертификатов, которые мы получаем от cardKeyStore в том же JTable, который мы использовали выше, вместе с сертификатами softKeyStore.

  2. Когда пользователь выбирает строку в JTable, получает выбранный псевдоним и сохраняет его в статическом поле.

  3. Когда нам нужно хранилище ключей для создания KeyManagerFactory и X509KeyManager для связи SSL, со статическим alias мы ищем его в softKeyStore, а затем cardKeyStore.

Нравиться:

if (softKeyStore.containsAlias(alias)) {
    return softKeyStore;
} else if (cardKeyStore.containsAlias(alias)) {
    return cardKeyStore;
}
  1. SSL-рукопожатие, отправка сообщений, получение, подписание и т. д.
person WesternGun    schedule 23.11.2016
comment
Есть идеи, как заставить его загружаться в MacOS? Я продолжал получать Multi-threaded initialization failed: sun.security.pkcs11.wrapper.PKCS11Exception: CKR_DEVICE_ERROR, странно то, что это работает, когда я пытаюсь прочитать хранилище ключей FF в Windows. Я заменил каталог lib, а также каталог профиля FF, чтобы использовать материалы MacOS, конечно, но все равно не пошел. - person codenamezero; 16.11.2017
comment
Ага.. Я не тестировал его на MacOS. Я видел твой пост и отвечу там. Некоторые догадки. - person WesternGun; 16.11.2017