Каков стандартный/современный способ использования аутентификации по карте CAC/PIV в веб-приложениях Java/Tomcat?

(См. важные примечания к редактированию в конце.)

Мы изучали карты CAC / PIV для нашего веб-приложения уже несколько дней и нашли некоторую полезную информацию, но нам все еще не хватает ее для наших конкретных нужд. Мы используем Spring Boot, Java и сервер Tomcat 9/10. По сути, наш вопрос: каков отраслевой стандарт для реализации аутентификации по карте CAC/PIV в веб-приложении Spring Boot, Java на сервере Tomcat? (Большинству источников, которые мы нашли, более 10 лет, а остальным 5-9 лет, поэтому нам просто нужна была актуальная информация по этой теме.)

В нашем веб-приложении будет два способа входа:

  1. Имя пользователя, пароль и ключ TOTP (у нас это работает)
  2. Имя пользователя, пароль и карта CAC/PIV

(Метод, необходимый для каждого пользователя, частично определяется в зависимости от его роли.)

У нас было несколько идей:

Идея №1: Конфигурация сервера

Мы нашли еще несколько человек, которые предлагают использовать настройку clientAuth = "true" на нашем сервере Tomcat или аналогичную. [1 ] [ 2 ] [ 3] Это было бы идеально, поскольку кажется, что это самый простой способ реализовать это, но поскольку нам нужны 2 метода, упомянутых выше, мы не хотите, чтобы приложение всегда требовало значок.

Примечание: кажется, есть возможность сказать clientAuth = "want", которая запросит сертификат пользователя, но не откажет пользователю, который не предъявит сертификат. Мне еще нужно узнать, как это работает. (Использовать "want" не рекомендуется, так как в некоторых случаях это может выдавать предупреждения.)

Еще одно замечание: использование clientAuth = "true" или "want" может оказаться успешным для некоторых, но на самом деле оно не дает нам нужного метода. По сути, он будет запрашивать сертификат с момента загрузки сервера. Если установлено значение "true", сервер не разрешит доступ к приложению, если он не примет предоставленный сертификат или не доверяет ему. Если установлено значение "want", сервер будет искать сертификат, но если ни один из них не найден (или ни один из них не является доверенным), он не будет хранить их, а просто позволит пользователю получить доступ к приложению как обычно. Однако после этого запросить сертификат (любыми настройками сервера) невозможно.

Чтобы обойти эту проблему, мы подумали, что могли бы создать своего рода «шлюзовое» приложение, единственной целью которого является выполнение аутентификации по бейджу. (Может быть, что-то вроде этого веб-сайта max.gov [4] или других сайтов департаментов, ссылки на которые есть на этой странице. Эта реализация на max.gov — это именно то, что нам нужно: возможность чтения бейджей или какой-либо другой метод.)

Нам удалось получить сертификат сервера, чтобы использовать его при тестировании этой идеи! Мы всё настроили и я разобрался с конфигурацией server.xml:

<!-- default connector to redirect to https port 8443 -->
<Connector executor = "tomcatThreadPool" port = "8080" protocol = "HTTP/1.1"
    connectionTimeout = "20000" redirectPort = "8443" maxParameterCount = "1000" />

<!-- connector structure for https -->
<Connector
    protocol = "org.apache.coyote.http11.Http11NioProtocol"
    port = "8444" maxThreads = "150" SSLEnabled = "true"
    maxParameterCount = "1000" secure = "true" scheme = "https" >
    
    <SSLHostConfig
        sslProtocol = "TLSv1.2"
        certificateVerification = "true" // true requires a cert, or else it fails
        truststoreFile = "C:/Program Files/Java/jdk-17/lib/security/cacerts"
        truststorePassword = "changeit">
        
        <Certificate
            certificateKeystoreFile = "path/to/keystore.jks"
            certificateKeystorePassword = "password" type = "RSA" />
    </SSLHostConfig>
</Connector>

Я все еще учусь, как заставить это работать с требованиями нашего приложения. (См. мой похожий вопрос [33] о том, как мы заставили часть этого процесса работать. Ссылка [ 3] отлично объяснила процесс/идею.)

Идея № 2: библиотеки с открытым исходным кодом

Используйте библиотеку с открытым исходным кодом из GitHub. Мы нашли несколько источников, которые рекомендуют это:

  • OpenSC [5 ]: Этот правительственный сайт [6] также ссылается на него, но это похоже на какое-то веб-расширение, но оно написано в основном на C.
  • веб-смарт-карта [7]: мы не знаем, как ее использовать.
  • PKCS#11: на это ссылаются еще несколько человек [8 ] [ 9 ] [ 10], но кажется, что это для настольных приложений - это правда? И некоторые сказали, что это может быть медленно? И ответ по ссылке № 9 использует сервлеты, которые не используются в приложениях Spring Boot (насколько я могу судить).
  • javax.smartcardio: эта библиотека, Smart Card IO [20], имеет несколько полезных функций, но есть два недостатка: 1) нет требования, чтобы все смарт-карты следовали одним и тем же командам/форматам APDU, и 2) у него возникают проблемы с правильной работой при развертывании веб-приложения на сервере, т. е. оно не может найти карту или устройство чтения.

Мы читали о некоторых случаях использования Java-апплетов, но это были старые обсуждения. Кроме того, вам, возможно, придется поработать с подписью апплета, а апплеты считаются несколько небезопасными.

Идея №3: запрос сертификата

Изучая более подробно реализацию аутентификации карт CAC/PIV в Login.gov, я понял, что ответ, данный по ссылке [11], на самом деле касается того, как выполняется аутентификация карт: «Вам будет гораздо легче разобраться в этом и найти информацию». если вы забудете, что пользователи используют смарт-карту или CAC, и просто начнете с идеи, что вы собираетесь использовать клиентские сертификаты для аутентификации».

Это обсуждение [22] может дать дополнительную информацию.

По сути, я узнал, что вам необходимо иметь URL-адрес https для вашего веб-приложения, чтобы ваш сервер мог устанавливать соединение SSL/TLS - это единственный способ запросить сертификат пользователя. Если вы это получите, теоретически вам просто нужно будет запросить сертификат X509 пользователя примерно так:

X509Certificate[] x509certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

if (x509certs != null && x509certs.length > 0) {
    X509Certificate x509cert = x509certs[0];
    try {
        x509cert.checkValidity();
    } catch (Exception e) {
        System.out.println("error checking cert or cert is not valid.");
    }
} else {
    System.out.println("certs list is null or empty.");
}

(Убедитесь, что вы изменили javax на jakarta, если вы используете Tomcat 10 или более позднюю версию.)

Кроме того, те, кто создает приложение для государственного учреждения, могут связаться с командой Login.gov на их сайте [17]. Если у вас или вашего агентства есть адрес электронной почты .gov, вы сможете создать учетную запись разработчика и использовать их систему для аутентификации пользователя.

Идея № 4: Прямое общение с картами

Карты CAC, PIV и другие считаются смарт-картами. Компьютеры обмениваются данными со смарт-картами через APDU (блоки данных протокола приложений) [18] . Есть несколько библиотек с открытым исходным кодом, которые могут работать, и у Oracle также есть кое-что: Java Card [19], Smart Card IO [20]. API Java Card в основном предназначен для создания и изменения карточек и обычно использует апплеты, что для нас не очень хороший выбор.

Используя ввод-вывод смарт-карты, я могу, по крайней мере, проверить, присутствует ли карта, и прочитать ATR карты:

try {
    // get all terminals
    TerminalFactory factory = TerminalFactory.getDefault();
    List<CardTerminal> terminals = factory.terminals().list();
    System.out.println("Terminals: " + terminals);
            
    // get the first terminal
    CardTerminal cdTerminal = terminals.get(0);
    
    try {
        if (cdTerminal.isCardPresent()) {
            // establish a connection with the card
            Card card = cdTerminal.connect("*"); // don't limit the type, just in case your card is different
            System.out.println("card: " + card);
            
            ATR atr = card.getATR();
            System.out.println("ATR: " + atr.getBytes().toString());
            
            card.disconnect(false);
        } else {
            System.out.println("No card inserted!");
        }
    
    } catch (CardException e) {
        System.out.println("Failed to determine if card is present.");
    }
} catch (CardException e1) {
    System.out.println("Failed in smart card io.");
}

Я надеялся использовать метод transmitControlCommand() [21] для получения сертификата, но сложно найти информацию о том, что означают эти два параметра и какие параметры для них доступны. (См. [24 ], [ 25 ], [ 26)].

Я изучаю это, но у кого-то есть репозиторий GitHub [27], который использует эту библиотеку, и я пытаюсь узнать, как они ее используют.

В этой библиотеке есть несколько полезных функций, но, к сожалению, есть два минуса:

  1. Не требуется, чтобы все смарт-карты использовали одни и те же команды/форматы APDU.
  2. Функции библиотеки имеют проблемы с правильной работой, когда веб-приложение развернуто на сервере, т. е. оно не может найти ни карту, ни устройство чтения.

Идея №5: Солнце PKCS#11

В этом обсуждении [23] и других, которые я видел, рекомендуется API Sun PKCS#11, но, очевидно, он устарел. Существуют возможные альтернативы (Bouncy Castle и IAIK PKCS#11 [28]), но все они требуют некоторой дополнительной собственной библиотеки PKCS#11.

Другие идеи:

Мне нравится обсуждение здесь [11], но оно более теоретическое, без каких-либо примеров кода. В любом случае, если кто-то еще ищет способ выполнить аутентификацию по бейджу, ответ дает лучшую основу для понимания того, как на самом деле работает аутентификация по бейджу!

Эти двое поднимают несколько хороших вопросов [12 ] [ 13], но ответов на них нет.

Некоторые используют node.js или React, например [это 14], но я слышал, что использовать это в приложении Spring Boot — не самое лучшее решение. Теперь я немного изучаю это, чтобы увидеть, что я могу использовать (особенно для проверки наличия карты).

Существует также Руководство разработчика [15] для устройства чтения карт, которое используется конечными пользователями этого приложения, но я пока не нашел там ничего полезного.

Мы также подумали об использовании доступной библиотеки от Okta, поскольку теперь у них есть возможность использовать методы аутентификации PIV-карты [16]. Однако похоже, что Okta — это платная услуга, и для нас это не вариант.

Я нашел рекомендацию попробовать API более низкого уровня, например PC/SC [29], но для этого также требуются команды APDU (которые могут зависеть от карты).

Правки

14 мая 2024 г.: Мы больше не будем использовать Spring Boot Framework, поскольку потребуется время для преобразования старого кода JDBC в новый способ с использованием bean-компонента jdbcTemplate. Мы также рассматриваем Tomcat 9 и 10 (для двух разных приложений с одинаковыми требованиями к входу в систему).

30 мая 2024 г.: Я провел дополнительные исследования и нашел множество хороших идей, но пока ни одна из них не оказалась полностью успешной. Я добавил примечания выше.

13.06.2024: (Разные изменения выше.) Я нашел простой способ прочитать сертификаты со смарт-карты! К сожалению, некоторые части у меня работают не так, как описано в источниках: у меня не появляется всплывающее окно выбора сертификата. ТАК вопросы, которые меня вдохновили, были этот [30] и этот [31], поэтому обязательно ознакомьтесь с ними, если мой вопрос вообще оказался для вас полезным!

Я сделал следующее:

// there are various types and providers, but these were the ones that worked
KeyStore keyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
// load local certificates (the certificates visible in your browser)
keyStore.load(null);
// get all the names of the available certificates
Enumeration<String> aliases = keyStore.aliases();

Теперь я могу просмотреть Enumeration, чтобы увидеть доступные сертификаты! Затем вы можете получить доступ к каждому из них, например

String als;
Certificate cert;
while (aliases.hasMoreElements()) {
    als = aliases.nextElement();
    System.out.println("Alias: " + als);
    cert = keyStore.getCertificate(als); // get the certificate
    // check the certificate
    certs = keyStore.getCertificateChain(als); // get the certificate chain
    // check the certificate chain
}

Есть несколько методов класса Certificate, которые вы можете использовать для проверки; например, cert.getPublicKey(), cert.getType() и cert.verify() (убедитесь, что вы используете открытый ключ эмитента для проверки [32]). Если cert instanceof X509Certificate, то способов гораздо больше!

Даже несмотря на этот прогресс, я до сих пор не нашел способа проверить PIN-код карты; это дает мне только общедоступную информацию из сертификата на карте.

24.06.2024: Я добавил несколько примечаний к идее №1, которая кажется лучшим вариантом!

Ссылки на ссылки:

  1. Аутентификация по карте общего доступа (CAC) с использованием Java
  2. Пытаюсь использовать смарт-карту для аутентификации в Tomcat
  3. https://blog.e-zest.com/enable-tomcat-server-for-smart-card-authentication
  4. https://login.max.gov/cas/login?bypassMaxsso=true
  5. https://github.com/OpenSC/OpenSC
  6. https://www.idmanagement.gov/implement/scl-firefox/
  7. https://github.com/WICG/web-smart-card
  8. https://stackoverflow.com/a/3826528/15811117
  9. Аутентификация по карте общего доступа (CAC) с помощью приложения Vaadin
  10. Аутентификация DoD PKI CAC в Tomcat (встроена в JBoss)
  11. Аутентификация CAC в веб-приложении Java
  12. Аутентификация CAC на сервере Tomcat
  13. Аутентифицируйте пользователя с помощью CAC (Common Access Card) в веб-приложении, работающем в Jetty, для приложения, используемого правительственным учреждением США
  14. https://marcioreis.pt/how-to-communicate-with-a-smartcard-reader-using-node-js/
  15. https://www.hidglobal.com/documents/omnikey-5422-and-5122-software-developer-guide
  16. https://www.okta.com/blog/2018/05/using-personal-identity-verification-piv-credentials-to-enable-passwordless-authentication/
  17. https://developers.login.gov/
  18. https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit
  19. https://docs.oracle.com/en/java/javacard/3.1/jc_api_srvc/api_classic/index.html
  20. https://docs.oracle.com/javase/8/docs/jre/api/security/smartcardio/spec/javax/smartcardio/package-summary.html
  21. https://docs.oracle.com/javase/8/docs/jre/api/security/smartcardio/spec/javax/smartcardio/Card.html#transmitControlCommand-int-byte:A-
  22. Как получить цепочку сертификатов сервера, а затем убедиться, что она действительна и надежна в Java
  23. API для чтения сертификата смарт-карты на Java
  24. https://forums.oracle.com/ords/apexds/post/what-is-the-controlcommand-4285
  25. https://forums.oracle.com/ords/apexds/post/use-secure-pin-commands-with-javax-smartcardio-1045
  26. https://stackoverflow.com/a/40673167/15811117
  27. https://github.com/jnasmartcardio/jnasmartcardio/blob/master/src/main/java/jnasmartcardio/Smartcardio.java
  28. Как инициализировать провайдера PKCS11 без использования SunPKCS11?
  29. https://pcscworkgroup.com/
  30. Подтвердите отмену keystore.load()
  31. Загрузить хранилище ключей Java для одного псевдонима?
  32. Проверка открытого ключа всегда возвращает «Подпись не совпадает»
  33. Как получить всплывающее меню для выбора сертификата пользователя на сервере Tomcat 10?

(Я буду продолжать обновлять этот вопрос по мере продолжения исследования.)

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
240
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короче говоря, идеи №1 и №3 – это решение проблемы.

Мой вопрос и ответ здесь также показывают некоторые важные части решения. Еще раз отмечу, что ссылка 11 в моем вопросе выше очень важна.

Настройте файл server.xml, как описано в моем ответе здесь, и используйте что-то вроде кода из идеи №3 выше:

X509Certificate[] x509certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

if (x509certs != null && x509certs.length > 0) {
    X509Certificate x509cert = x509certs[0];
    try {
        x509cert.checkValidity();
    } catch (Exception e) {
        System.out.println("error checking cert or cert is not valid.");
    }
} else {
    System.out.println("certs list is null or empty.");
}

Примечание: браузер предлагает пользователю выбрать свой сертификат и ввести свой, а не веб-приложение. Затем сервер получает сертификат (цепочку) от браузера.

Другие вопросы по теме